diff --git a/lunaria/components.ts b/lunaria/components.ts index e5545004f8..62dc722635 100644 --- a/lunaria/components.ts +++ b/lunaria/components.ts @@ -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` @@ -43,7 +44,7 @@ export const Page = ( ${Meta} ${BaseStyles} ${CustomStyles} - ${Body(config, status, lunaria)} + ${Body(config, status)} ` @@ -73,144 +74,135 @@ export const Meta = html` ` -export const Body = ( - config: LunariaConfig, - status: LunariaStatus, - lunaria: LunariaInstance, -): string => { +export const Body = (config: LunariaConfig, status: I18nStatus): string => { return html`

npmx Translation Status

- ${TitleParagraph} ${StatusByLocale(config, status, lunaria)} + ${TitleParagraph} ${StatusByLocale(config, status)}
- ${StatusByFile(config, status, lunaria)}
` } -export const StatusByLocale = ( - config: LunariaConfig, - status: LunariaStatus, - lunaria: LunariaInstance, -): string => { +export const StatusByLocale = (config: LunariaConfig, status: I18nStatus): string => { const { locales } = config return html`

Translation progress by locale

- ${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`
- ${label} (${lang}) -
- - ${doneLength.toString()} done, ${outdatedFiles.length.toString()} outdated, - ${missingFiles.length.toString()} missing - -
- ${ProgressBar(status.length, outdatedFiles.length, missingFiles.length)} + ${label} ${lang} +
+
+ + ${missingKeys.length ? `${missingKeys.length.toString()} missing keys` : '✔'} + + ${completedKeys} / ${totalKeys} +
+ ${ProgressBar(percentComplete)}
- ${outdatedFiles.length > 0 ? OutdatedFiles(outdatedFiles, lang, lunaria) : ''} +
+ ${ContentDetailsLinks({ text: `i18n/locales/${lang}.json`, url: githubEditUrl }, githubHistoryUrl)} +
+
${ - missingFiles.length > 0 - ? html`

Missing

- ` - : '' - } - ${ - missingFiles.length == 0 && outdatedFiles.length == 0 - ? html` + missingKeys.length > 0 + ? html`${MissingKeysList(missingKeys)}` + : html`

This translation is complete, amazing job! 🎉

` - : '' }
` } -export const OutdatedFiles = ( - outdatedFiles: LunariaStatus, - lang: string, - lunaria: LunariaInstance, +export const MissingKeysList = (missingKeys: string[]): string => { + return html`
+ Show missing keys + +
` +} + +export const ContentDetailsLinks = ( + githubEditLink: { text: string; url: string }, + githubHistoryUrl: string, ): string => { return html` -

Outdated

- + ${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` + ` } +export const Link = (href: string, text: string): string => { + return html`${text}` +} + +export const TitleParagraph = html` +

+ If you're interested in helping us translate + npmx.dev 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. +

+

+ Before starting, please read our + localization (i18n) guide + to learn about our translation process and how you can get involved. +

+` + +// 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`${EmojiFileLink(link, status)}` } -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 = ( ` } -export const Link = (href: string, text: string): string => { - return html`${text}` -} - export const CreateFileLink = (href: string, text: string): string => { return html`${text}` } -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`
`) - } - return items - } - - return html` - - ` -} - -export const TitleParagraph = html` -

- If you're interested in helping us translate - npmx.dev 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. -

-

- Before starting, please read our - localization (i18n) guide - to learn about our translation process and how you can get involved. -

-` - /** * 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') { diff --git a/lunaria/lunaria.ts b/lunaria/lunaria.ts index de8ee48073..8bba947814 100644 --- a/lunaria/lunaria.ts +++ b/lunaria/lunaria.ts @@ -14,9 +14,6 @@ if (existsSync('.git/MERGE_HEAD')) { const lunaria = await createLunaria() const status = await lunaria.getFullStatus() -// Generate HTML dashboard -const html = Page(lunaria.config, status, lunaria) - // Generate JSON status for the app const { sourceLocale } = lunaria.config const links = lunaria.gitHostingLinks() @@ -103,6 +100,9 @@ const jsonStatus: I18nStatus = { }), } +// Generate HTML dashboard using processed jsonStatus +const html = Page(lunaria.config, jsonStatus, lunaria) + mkdirSync('dist/lunaria', { recursive: true }) writeFileSync('dist/lunaria/index.html', html) writeFileSync('dist/lunaria/status.json', JSON.stringify(jsonStatus, null, 2)) diff --git a/lunaria/styles.ts b/lunaria/styles.ts index f12df0e783..dc46647ea9 100644 --- a/lunaria/styles.ts +++ b/lunaria/styles.ts @@ -23,15 +23,22 @@ export const BaseStyles = html` --ln-color-blue: #3b82f6; --ln-color-orange: #f97316; --ln-color-purple: #a855f7; + --ln-color-green: #2edaa6; /* swatch-emerald */ + --ln-color-dark-green: #24a27c; + --ln-color-red: #f9697c; /* swatch-coral */ + --ln-color-dark-red: #bf002d; /** Contextual colors */ --ln-color-background: var(--ln-color-white); - --ln-color-link: var(--ln-color-blue); + --ln-color-link: var(--ln-color-gray-2); + --ln-color-link-hover: var(--ln-color-white); --ln-color-done: var(--ln-color-purple); --ln-color-outdated: var(--ln-color-orange); --ln-color-missing: #ef4444; --ln-color-table-border: var(--ln-color-gray-3); --ln-color-table-background: var(--ln-color-gray-1); + + --progress-bar-height: 16px; } @media (prefers-color-scheme: dark) { @@ -128,27 +135,37 @@ export const BaseStyles = html` color: inherit; } + a { + color: var(--ln-color-link); + } + a:hover { text-decoration: underline; - color: var(--ln-color-gray-5); + color: var(--ln-color-link-hover); } ul { font-size: 0.875rem; } - .progress-details { - margin-bottom: 1.25rem; + .capitalize { + text-transform: capitalize; } details summary { cursor: pointer; user-select: none; + color: var(--ln-color-link); } + details summary::marker { + margin-right: 0.4rem; + } + + details summary:hover, details summary:hover strong, details summary:hover::marker { - color: var(--ln-color-gray-5); + color: var(--ln-color-link-hover); } details p { @@ -169,12 +186,75 @@ export const BaseStyles = html` margin-bottom: 1rem; } + .lang-code { + margin-left: 1rem; + } + + /* Progress deatils per locale */ + .progress-details { + border: 1px solid var(--ln-color-gray-6); + margin-bottom: 1.25rem; + padding: 1rem; + border-radius: 0.5rem; + } + + .progress-details hr { + margin-top: 0.5rem; + border-color: var(--ln-color-gray-6); + } + + .progress-details a { + font-size: 0.85rem; + } + + .progress-summary { + display: flex; + justify-content: space-between; + padding-top: 0.5rem; + font-size: 0.8125rem; + } + + .progress-bar-wrapper { + height: var(--progress-bar-height); + margin-top: 0.5rem; + border-radius: var(--progress-bar-height); + background-color: var(--ln-color-gray-7); + } + + .progress-bar-wrapper .progress-bar { + min-width: 5%; + max-width: 100%; + height: var(--progress-bar-height); + border-radius: var(--progress-bar-height); + } + + .progress-bar.completed { + background-color: var(--ln-color-dark-green); + } + + .progress-bar.very-good { + background-color: var(--ln-color-green); + } + + .progress-bar.good { + background-color: var(--ln-color-orange); + } + + .progress-bar.help-needed { + background-color: var(--ln-color-red); + } + + .progress-bar.basic { + background-color: var(--ln-color-dark-red); + } + .create-button { padding: 0.1em 0.5em; font-weight: bold; font-size: 0.75rem; } + /*Progress by files*/ .status-by-file-wrapper { overflow-x: auto; margin-bottom: 1rem; @@ -239,47 +319,6 @@ export const BaseStyles = html` .status-by-file td:not(:first-of-type) a { text-decoration: none; } - - .progress-summary { - font-size: 0.8125rem; - } - - .progress-bar { - display: flex; - flex-direction: row; - margin-top: 0.5rem; - } - - .progress-bar div:first-of-type { - border-radius: 36px 0px 0px 36px; - } - - .progress-bar div:last-of-type { - border-radius: 0px 36px 36px 0px; - } - - .up-to-date-bar, - .outdated-bar, - .missing-bar { - width: 1rem; - height: 1rem; - } - - .up-to-date-bar { - background-color: var(--ln-color-done); - } - - .outdated-bar { - background-color: var(--ln-color-outdated); - } - - .missing-bar { - background-color: var(--ln-color-missing); - } - - .capitalize { - text-transform: capitalize; - } ` @@ -298,14 +337,10 @@ export const CustomStyles = html` --border-subtle: oklch(23.9% 0 0); --border-hover: oklch(37.1% 0 0); - --ln-color-link: #539bf5; --ln-color-table-background: var(--bg-subtle); --ln-color-table-border: var(--border); --ln-color-background: var(--bg); --ln-color-black: var(--fg); - --ln-color-missing: #f87171; - --ln-color-outdated: #fb923c; - --ln-color-done: #c084fc; } html {