-
-
Notifications
You must be signed in to change notification settings - Fork 335
feat: improve i18n (lunaria) status page #2064
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import { | |
| type StatusEntry, | ||
| } from '@lunariajs/core' | ||
| import { BaseStyles, CustomStyles } from './styles.ts' | ||
| import type { I18nStatus } from '../shared/types/i18n-status.ts' | ||
|
|
||
| export function html( | ||
| strings: TemplateStringsArray, | ||
|
|
@@ -33,8 +34,8 @@ function collapsePath(path: string) { | |
|
|
||
| export const Page = ( | ||
| config: LunariaConfig, | ||
| status: LunariaStatus, | ||
| lunaria: LunariaInstance, | ||
| status: I18nStatus, | ||
| _lunaria: LunariaInstance, // currenly not in use | ||
| ): string => { | ||
| return html` | ||
| <!doctype html> | ||
|
|
@@ -43,7 +44,7 @@ export const Page = ( | |
| ${Meta} ${BaseStyles} ${CustomStyles} | ||
| </head> | ||
| <body> | ||
| ${Body(config, status, lunaria)} | ||
| ${Body(config, status)} | ||
| </body> | ||
| </html> | ||
| ` | ||
|
|
@@ -73,144 +74,135 @@ export const Meta = html` | |
| <link rel="icon" href="https://npmx.dev/favicon.svg" type="image/svg+xml" /> | ||
| ` | ||
|
|
||
| export const Body = ( | ||
| config: LunariaConfig, | ||
| status: LunariaStatus, | ||
| lunaria: LunariaInstance, | ||
| ): string => { | ||
| export const Body = (config: LunariaConfig, status: I18nStatus): string => { | ||
| return html` | ||
| <main> | ||
| <div class="limit-to-viewport"> | ||
| <h1>npmx Translation Status</h1> | ||
| ${TitleParagraph} ${StatusByLocale(config, status, lunaria)} | ||
| ${TitleParagraph} ${StatusByLocale(config, status)} | ||
| </div> | ||
| ${StatusByFile(config, status, lunaria)} | ||
| </main> | ||
| ` | ||
| } | ||
|
|
||
| export const StatusByLocale = ( | ||
| config: LunariaConfig, | ||
| status: LunariaStatus, | ||
| lunaria: LunariaInstance, | ||
| ): string => { | ||
| export const StatusByLocale = (config: LunariaConfig, status: I18nStatus): string => { | ||
| const { locales } = config | ||
| return html` | ||
| <h2 id="by-locale"> | ||
| <a href="#by-locale">Translation progress by locale</a> | ||
| </h2> | ||
| ${locales.map(locale => LocaleDetails(status, locale, lunaria))} | ||
| ${locales.map(locale => LocaleDetails(status, locale))} | ||
| ` | ||
| } | ||
|
|
||
| export const LocaleDetails = ( | ||
| status: LunariaStatus, | ||
| locale: Locale, | ||
| lunaria: LunariaInstance, | ||
| ): string => { | ||
| export const LocaleDetails = (status: I18nStatus, locale: Locale): string => { | ||
| const { label, lang } = locale | ||
| const localeStatus = status.locales.find(s => s.lang === lang) | ||
|
|
||
| const missingFiles = status.filter( | ||
| file => | ||
| file.localizations.find(localization => localization.lang === lang)?.status === 'missing', | ||
| ) | ||
| const outdatedFiles = status.filter(file => { | ||
| const localization = file.localizations.find(localization => localization.lang === lang) | ||
|
|
||
| if (!localization || localization.status === 'missing') return false | ||
| if (file.type === 'dictionary') | ||
| return 'missingKeys' in localization ? localization.missingKeys.length > 0 : false | ||
|
|
||
| return ( | ||
| localization.status === 'outdated' || | ||
| ('missingKeys' in localization && localization.missingKeys.length > 0) | ||
| ) | ||
| }) | ||
|
|
||
| const doneLength = status.length - outdatedFiles.length - missingFiles.length | ||
| if (!localeStatus) { | ||
| return '' | ||
| } | ||
|
|
||
| const links = lunaria.gitHostingLinks() | ||
| const { | ||
| missingKeys, | ||
| percentComplete, | ||
| totalKeys, | ||
| completedKeys, | ||
| githubEditUrl, | ||
| githubHistoryUrl, | ||
| } = localeStatus | ||
|
|
||
| return html` | ||
| <details class="progress-details"> | ||
| <summary> | ||
| <strong>${label} (${lang})</strong> | ||
| <br /> | ||
| <span class="progress-summary"> | ||
| ${doneLength.toString()} done, ${outdatedFiles.length.toString()} outdated, | ||
| ${missingFiles.length.toString()} missing | ||
| </span> | ||
| <br /> | ||
| ${ProgressBar(status.length, outdatedFiles.length, missingFiles.length)} | ||
| <strong>${label} <span class="lang-code">${lang}</span></strong> | ||
| <hr /> | ||
| <div class="progress-summary"> | ||
| <span> | ||
| ${missingKeys.length ? `${missingKeys.length.toString()} missing keys` : '✔'} | ||
| </span> | ||
| <span>${completedKeys} / ${totalKeys}</span> | ||
| </div> | ||
| ${ProgressBar(percentComplete)} | ||
| </summary> | ||
| ${outdatedFiles.length > 0 ? OutdatedFiles(outdatedFiles, lang, lunaria) : ''} | ||
| <br /> | ||
| ${ContentDetailsLinks({ text: `i18n/locales/${lang}.json`, url: githubEditUrl }, githubHistoryUrl)} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Render the resolved locale filename here. The edit/history URLs are already built from the resolved path, but this label is reconstructed from |
||
| <br /> | ||
| <br /> | ||
| ${ | ||
| missingFiles.length > 0 | ||
| ? html`<h3 class="capitalize">Missing</h3> | ||
| <ul> | ||
| ${missingFiles.map(file => { | ||
| const localization = file.localizations.find( | ||
| localization => localization.lang === lang, | ||
| )! | ||
| return html` | ||
| <li> | ||
| ${Link(links.source(file.source.path), collapsePath(file.source.path))} | ||
| ${CreateFileLink(links.create(localization.path), 'Create file')} | ||
| </li> | ||
| ` | ||
| })} | ||
| </ul>` | ||
| : '' | ||
| } | ||
| ${ | ||
| missingFiles.length == 0 && outdatedFiles.length == 0 | ||
| ? html` | ||
| missingKeys.length > 0 | ||
| ? html`${MissingKeysList(missingKeys)}` | ||
| : html` | ||
| <p>This translation is complete, amazing job! 🎉</p> | ||
| ` | ||
| : '' | ||
| } | ||
| </details> | ||
| ` | ||
| } | ||
|
|
||
| export const OutdatedFiles = ( | ||
| outdatedFiles: LunariaStatus, | ||
| lang: string, | ||
| lunaria: LunariaInstance, | ||
| export const MissingKeysList = (missingKeys: string[]): string => { | ||
| return html`<details> | ||
| <summary>Show missing keys</summary> | ||
| <ul> | ||
| ${missingKeys.map(key => html`<li>${key}</li>`)} | ||
| </ul> | ||
| </details>` | ||
| } | ||
|
|
||
| export const ContentDetailsLinks = ( | ||
| githubEditLink: { text: string; url: string }, | ||
| githubHistoryUrl: string, | ||
| ): string => { | ||
| return html` | ||
| <h3 class="capitalize">Outdated</h3> | ||
| <ul> | ||
| ${outdatedFiles.map(file => { | ||
| const localization = file.localizations.find(localization => localization.lang === lang)! | ||
|
|
||
| const isMissingKeys = | ||
| localization.status !== 'missing' && | ||
| 'missingKeys' in localization && | ||
| localization.missingKeys.length > 0 | ||
|
|
||
| return html` | ||
| <li> | ||
| ${ | ||
| isMissingKeys | ||
| ? html` | ||
| <details> | ||
| <summary>${ContentDetailsLinks(file, lang, lunaria)}</summary> | ||
| <h4>Missing keys</h4> | ||
| <ul> | ||
| ${localization.missingKeys.map(key => html`<li>${(key as unknown as string[]).join('.')}</li>`)} | ||
| </ul> | ||
| </details> | ||
| ` | ||
| : html` ${ContentDetailsLinks(file, lang, lunaria)} ` | ||
| } | ||
| </li> | ||
| ` | ||
| })} | ||
| </ul> | ||
| ${Link(githubEditLink.url, githubEditLink.text)} | | ||
| ${Link(githubHistoryUrl, 'source change history')} | ||
| ` | ||
| } | ||
|
|
||
| export const ProgressBar = (percentComplete: number): string => { | ||
| let barClass = 'completed' | ||
|
|
||
| if (percentComplete > 99) { | ||
| barClass = 'completed' // dark-green | ||
| } else if (percentComplete > 90) { | ||
| barClass = 'very-good' // green | ||
| } else if (percentComplete > 75) { | ||
| barClass = 'good' // orange | ||
| } else if (percentComplete > 50) { | ||
| barClass = 'help-needed' // red | ||
| } else { | ||
| barClass = 'basic' // dark-red | ||
| } | ||
|
|
||
| return html` | ||
| <div class="progress-bar-wrapper" aria-hidden="true"> | ||
| <div class="progress-bar ${barClass}" style="width:${percentComplete}%;"></div> | ||
| </div> | ||
| ` | ||
| } | ||
|
|
||
| export const Link = (href: string, text: string): string => { | ||
| return html`<a href="${href}" target="_blank">${text}</a>` | ||
| } | ||
|
|
||
| export const TitleParagraph = html` | ||
| <p> | ||
| If you're interested in helping us translate | ||
| <a href="https://npmx.dev/">npmx.dev</a> into one of the languages listed below, you've come to | ||
| the right place! This auto-updating page always lists all the content that could use your help | ||
| right now. | ||
| </p> | ||
| <p> | ||
| Before starting, please read our | ||
| <a href="https://github.com/npmx-dev/npmx.dev/blob/main/CONTRIBUTING.md#localization-i18n" | ||
| >localization (i18n) guide</a | ||
| > | ||
| to learn about our translation process and how you can get involved. | ||
| </p> | ||
| ` | ||
|
|
||
| // Components from here are not used at the moment | ||
| // Do not delete as we might use it if we split translations in multiple files for locale | ||
| export const StatusByFile = ( | ||
| config: LunariaConfig, | ||
| status: LunariaStatus, | ||
|
|
@@ -265,7 +257,7 @@ export const TableContentStatus = ( | |
| lunaria: LunariaInstance, | ||
| fileType?: string, | ||
| ): string => { | ||
| const localization = localizations.find(localization => localization.lang === lang)! | ||
| const localization = localizations.find(l => l.lang === lang)! | ||
| const isMissingKeys = 'missingKeys' in localization && localization.missingKeys.length > 0 | ||
| // For dictionary files, status is determined solely by key completion: | ||
| // if there are missing keys it's "outdated", if all keys are present it's "up-to-date", | ||
|
|
@@ -287,37 +279,6 @@ export const TableContentStatus = ( | |
| return html`<td>${EmojiFileLink(link, status)}</td>` | ||
| } | ||
|
|
||
| export const ContentDetailsLinks = ( | ||
| fileStatus: StatusEntry, | ||
| lang: string, | ||
| lunaria: LunariaInstance, | ||
| ): string => { | ||
| const localization = fileStatus.localizations.find(localization => localization.lang === lang)! | ||
| const isMissingKeys = | ||
| localization.status !== 'missing' && | ||
| 'missingKeys' in localization && | ||
| localization.missingKeys.length > 0 | ||
|
|
||
| const links = lunaria.gitHostingLinks() | ||
|
|
||
| return html` | ||
| ${Link(links.source(fileStatus.source.path), collapsePath(fileStatus.source.path))} | ||
| (${Link( | ||
| links.source(localization.path), | ||
| isMissingKeys ? 'incomplete translation' : 'outdated translation', | ||
| )}, | ||
| ${Link( | ||
| links.history( | ||
| fileStatus.source.path, | ||
| 'git' in localization | ||
| ? new Date(localization.git.latestTrackedCommit.date).toISOString() | ||
| : undefined, | ||
| ), | ||
| 'source change history', | ||
| )}) | ||
| ` | ||
| } | ||
|
|
||
| export const EmojiFileLink = ( | ||
| href: string | null, | ||
| type: 'missing' | 'outdated' | 'up-to-date', | ||
|
|
@@ -343,56 +304,10 @@ export const EmojiFileLink = ( | |
| </span>` | ||
| } | ||
|
|
||
| export const Link = (href: string, text: string): string => { | ||
| return html`<a href="${href}">${text}</a>` | ||
| } | ||
|
|
||
| export const CreateFileLink = (href: string, text: string): string => { | ||
| return html`<a class="create-button" href="${href}">${text}</a>` | ||
| } | ||
|
|
||
| export const ProgressBar = ( | ||
| total: number, | ||
| outdated: number, | ||
| missing: number, | ||
| { size = 20 }: { size?: number } = {}, | ||
| ): string => { | ||
| const outdatedSize = Math.round((outdated / total) * size) | ||
| const missingSize = Math.round((missing / total) * size) | ||
| const doneSize = size - outdatedSize - missingSize | ||
|
|
||
| const getBlocks = (size: number, type: 'missing' | 'outdated' | 'up-to-date') => { | ||
| const items = [] | ||
| for (let i = 0; i < size; i++) { | ||
| items.push(html`<div class="${type}-bar"></div>`) | ||
| } | ||
| return items | ||
| } | ||
|
|
||
| return html` | ||
| <div class="progress-bar" aria-hidden="true"> | ||
| ${getBlocks(doneSize, 'up-to-date')} ${getBlocks(outdatedSize, 'outdated')} | ||
| ${getBlocks(missingSize, 'missing')} | ||
| </div> | ||
| ` | ||
| } | ||
|
|
||
| export const TitleParagraph = html` | ||
| <p> | ||
| If you're interested in helping us translate | ||
| <a href="https://npmx.dev/">npmx.dev</a> into one of the languages listed below, you've come to | ||
| the right place! This auto-updating page always lists all the content that could use your help | ||
| right now. | ||
| </p> | ||
| <p> | ||
| Before starting, please read our | ||
| <a href="https://github.com/npmx-dev/npmx.dev/blob/main/CONTRIBUTING.md#localization-i18n" | ||
| >localization (i18n) guide</a | ||
| > | ||
| to learn about our translation process and how you can get involved. | ||
| </p> | ||
| ` | ||
|
|
||
| /** | ||
| * Build an SVG file showing a summary of each language's translation progress. | ||
| */ | ||
|
|
@@ -421,11 +336,10 @@ function SvgLocaleSummary( | |
| { label, lang }: Locale, | ||
| ): { svg: string; progress: number } { | ||
| const missingFiles = status.filter( | ||
| file => | ||
| file.localizations.find(localization => localization.lang === lang)?.status === 'missing', | ||
| file => file.localizations.find(l => l.lang === lang)?.status === 'missing', | ||
| ) | ||
| const outdatedFiles = status.filter(file => { | ||
| const localization = file.localizations.find(localization => localization.lang === lang) | ||
| const localization = file.localizations.find(l => l.lang === lang) | ||
| if (!localization || localization.status === 'missing') { | ||
| return false | ||
| } else if (file.type === 'dictionary') { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.