import { type createLunaria, type Locale, type LunariaConfig, type LunariaStatus, type StatusEntry, } from '@lunariajs/core' import { BaseStyles, CustomStyles } from './styles.ts' export function html( strings: TemplateStringsArray, ...values: ((string | number) | (string | number)[])[] ) { const treatedValues = values.map(value => (Array.isArray(value) ? value.join('') : value)) return String.raw({ raw: strings }, ...treatedValues) } type LunariaInstance = Awaited> function collapsePath(path: string) { const basesToHide = ['src/content/docs/en/', 'src/i18n/en/', 'src/content/docs/', 'src/content/'] for (const base of basesToHide) { const newPath = path.replace(base, '') if (newPath === path) continue return newPath } return path } export const Page = ( config: LunariaConfig, status: LunariaStatus, lunaria: LunariaInstance, ): string => { return html` ${Meta} ${BaseStyles} ${CustomStyles} ${Body(config, status, lunaria)} ` } export const Meta = html` npmx - Translation Status ` export const Body = ( config: LunariaConfig, status: LunariaStatus, lunaria: LunariaInstance, ): string => { return html`

npmx Translation Status

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

Translation progress by locale

${locales.map(locale => LocaleDetails(status, locale, lunaria))} ` } export const LocaleDetails = ( status: LunariaStatus, locale: Locale, lunaria: LunariaInstance, ): string => { const { label, lang } = locale 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 const links = lunaria.gitHostingLinks() return html`
${label} (${lang})
${doneLength.toString()} done, ${outdatedFiles.length.toString()} outdated, ${missingFiles.length.toString()} missing
${ProgressBar(status.length, outdatedFiles.length, missingFiles.length)}
${outdatedFiles.length > 0 ? OutdatedFiles(outdatedFiles, lang, lunaria) : ''} ${ missingFiles.length > 0 ? html`

Missing

` : '' } ${ missingFiles.length == 0 && outdatedFiles.length == 0 ? html`

This translation is complete, amazing job! πŸŽ‰

` : '' }
` } export const OutdatedFiles = ( outdatedFiles: LunariaStatus, lang: string, lunaria: LunariaInstance, ): string => { return html`

Outdated

` } export const StatusByFile = ( config: LunariaConfig, status: LunariaStatus, lunaria: LunariaInstance, ): string => { const { locales } = config return html`

Translation status by file

${['File', ...locales.map(({ lang }) => lang)].map(col => html``)} ${TableBody(status, locales, lunaria)}
${col}
❌ missing   πŸ”„ outdated   βœ” done ` } export const TableBody = ( status: LunariaStatus, locales: Locale[], lunaria: LunariaInstance, ): string => { const links = lunaria.gitHostingLinks() return html` ${status.map( file => html` ${Link(links.source(file.source.path), collapsePath(file.source.path))} ${locales.map(({ lang }) => { return TableContentStatus(file.localizations, lang, lunaria) })} `, )} ` } export const TableContentStatus = ( localizations: StatusEntry['localizations'], lang: string, lunaria: LunariaInstance, ): string => { const localization = localizations.find(localization => localization.lang === lang)! const isMissingKeys = 'missingKeys' in localization && localization.missingKeys.length > 0 const status = isMissingKeys ? 'outdated' : localization.status const links = lunaria.gitHostingLinks() const link = status === 'missing' ? links.create(localization.path) : links.source(localization.path) 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', ): string => { const statusTextOpts = { 'missing': 'missing', 'outdated': 'outdated', 'up-to-date': 'done', } as const const statusEmojiOpts = { 'missing': '❌', 'outdated': 'πŸ”„', 'up-to-date': 'βœ”', } as const return href ? html` ` : html` ` } 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. */ export const SvgSummary = (config: LunariaConfig, status: LunariaStatus): string => { const localeHeight = 56 // Each locale’s summary is 56px high. const svgHeight = localeHeight * Math.ceil(config.locales.length / 2) return html` ${config.locales .map(locale => SvgLocaleSummary(status, locale)) .sort((a, b) => b.progress - a.progress) .map( ({ svg }, index) => html`${svg}`, )} ` } function SvgLocaleSummary( status: LunariaStatus, { label, lang }: Locale, ): { svg: string; progress: number } { 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 } else if (file.type === 'dictionary') { return 'missingKeys' in localization ? localization.missingKeys.length > 0 : false } else { return ( localization.status === 'outdated' || ('missingKeys' in localization && localization.missingKeys.length > 0) ) } }) const doneLength = status.length - outdatedFiles.length - missingFiles.length const barWidth = 184 const doneFraction = doneLength / status.length const outdatedFraction = outdatedFiles.length / status.length const doneWidth = (doneFraction * barWidth).toFixed(2) const outdatedWidth = ((outdatedFraction + doneFraction) * barWidth).toFixed(2) return { progress: doneFraction, svg: html`${label} (${lang}) ${ missingFiles.length == 0 && outdatedFiles.length == 0 ? '100% complete, amazing job! πŸŽ‰' : html`${doneLength} done, ${outdatedFiles.length} outdated, ${missingFiles.length} missing` } `, } }