Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
282 changes: 98 additions & 184 deletions lunaria/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>
Expand All @@ -43,7 +44,7 @@ export const Page = (
${Meta} ${BaseStyles} ${CustomStyles}
</head>
<body>
${Body(config, status, lunaria)}
${Body(config, status)}
</body>
</html>
`
Expand Down Expand Up @@ -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 ''
}
Comment thread
alex-key marked this conversation as resolved.

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)}
Comment thread
alex-key marked this conversation as resolved.
</summary>
${outdatedFiles.length > 0 ? OutdatedFiles(outdatedFiles, lang, lunaria) : ''}
<br />
${ContentDetailsLinks({ text: `i18n/locales/${lang}.json`, url: githubEditUrl }, githubHistoryUrl)}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<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>`
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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,
Expand Down Expand Up @@ -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",
Expand All @@ -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',
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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') {
Expand Down
Loading
Loading