Skip to content

Commit e4549b9

Browse files
committed
feat: improve usability and design on lunaria/i18n status page
1 parent 8976969 commit e4549b9

3 files changed

Lines changed: 186 additions & 237 deletions

File tree

lunaria/components.ts

Lines changed: 98 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
type StatusEntry,
77
} from '@lunariajs/core'
88
import { BaseStyles, CustomStyles } from './styles.ts'
9+
import type { I18nStatus } from '../shared/types/i18n-status.ts'
910

1011
export function html(
1112
strings: TemplateStringsArray,
@@ -33,8 +34,8 @@ function collapsePath(path: string) {
3334

3435
export const Page = (
3536
config: LunariaConfig,
36-
status: LunariaStatus,
37-
lunaria: LunariaInstance,
37+
status: I18nStatus,
38+
_lunaria: LunariaInstance, // currenly not in use
3839
): string => {
3940
return html`
4041
<!doctype html>
@@ -43,7 +44,7 @@ export const Page = (
4344
${Meta} ${BaseStyles} ${CustomStyles}
4445
</head>
4546
<body>
46-
${Body(config, status, lunaria)}
47+
${Body(config, status)}
4748
</body>
4849
</html>
4950
`
@@ -73,144 +74,135 @@ export const Meta = html`
7374
<link rel="icon" href="https://npmx.dev/favicon.svg" type="image/svg+xml" />
7475
`
7576

76-
export const Body = (
77-
config: LunariaConfig,
78-
status: LunariaStatus,
79-
lunaria: LunariaInstance,
80-
): string => {
77+
export const Body = (config: LunariaConfig, status: I18nStatus): string => {
8178
return html`
8279
<main>
8380
<div class="limit-to-viewport">
8481
<h1>npmx Translation Status</h1>
85-
${TitleParagraph} ${StatusByLocale(config, status, lunaria)}
82+
${TitleParagraph} ${StatusByLocale(config, status)}
8683
</div>
87-
${StatusByFile(config, status, lunaria)}
8884
</main>
8985
`
9086
}
9187

92-
export const StatusByLocale = (
93-
config: LunariaConfig,
94-
status: LunariaStatus,
95-
lunaria: LunariaInstance,
96-
): string => {
88+
export const StatusByLocale = (config: LunariaConfig, status: I18nStatus): string => {
9789
const { locales } = config
9890
return html`
9991
<h2 id="by-locale">
10092
<a href="#by-locale">Translation progress by locale</a>
10193
</h2>
102-
${locales.map(locale => LocaleDetails(status, locale, lunaria))}
94+
${locales.map(locale => LocaleDetails(status, locale))}
10395
`
10496
}
10597

106-
export const LocaleDetails = (
107-
status: LunariaStatus,
108-
locale: Locale,
109-
lunaria: LunariaInstance,
110-
): string => {
98+
export const LocaleDetails = (status: I18nStatus, locale: Locale): string => {
11199
const { label, lang } = locale
100+
const localeStatus = status.locales.find(s => s.lang === lang)
112101

113-
const missingFiles = status.filter(
114-
file =>
115-
file.localizations.find(localization => localization.lang === lang)?.status === 'missing',
116-
)
117-
const outdatedFiles = status.filter(file => {
118-
const localization = file.localizations.find(localization => localization.lang === lang)
119-
120-
if (!localization || localization.status === 'missing') return false
121-
if (file.type === 'dictionary')
122-
return 'missingKeys' in localization ? localization.missingKeys.length > 0 : false
123-
124-
return (
125-
localization.status === 'outdated' ||
126-
('missingKeys' in localization && localization.missingKeys.length > 0)
127-
)
128-
})
129-
130-
const doneLength = status.length - outdatedFiles.length - missingFiles.length
102+
if (!localeStatus) {
103+
return ''
104+
}
131105

132-
const links = lunaria.gitHostingLinks()
106+
const {
107+
missingKeys,
108+
percentComplete,
109+
totalKeys,
110+
completedKeys,
111+
githubEditUrl,
112+
githubHistoryUrl,
113+
} = localeStatus
133114

134115
return html`
135116
<details class="progress-details">
136117
<summary>
137-
<strong>${label} (${lang})</strong>
138-
<br />
139-
<span class="progress-summary">
140-
${doneLength.toString()} done, ${outdatedFiles.length.toString()} outdated,
141-
${missingFiles.length.toString()} missing
142-
</span>
143-
<br />
144-
${ProgressBar(status.length, outdatedFiles.length, missingFiles.length)}
118+
<strong>${label} <span class="lang-code">${lang}</span></strong>
119+
<hr />
120+
<div class="progress-summary">
121+
<span>
122+
${missingKeys.length ? `${missingKeys.length.toString()} missing keys` : '✔'}
123+
</span>
124+
<span>${completedKeys} / ${totalKeys}</span>
125+
</div>
126+
${ProgressBar(percentComplete)}
145127
</summary>
146-
${outdatedFiles.length > 0 ? OutdatedFiles(outdatedFiles, lang, lunaria) : ''}
128+
<br />
129+
${ContentDetailsLinks({ text: `i18n/locales/${lang}.json`, url: githubEditUrl }, githubHistoryUrl)}
130+
<br />
131+
<br />
147132
${
148-
missingFiles.length > 0
149-
? html`<h3 class="capitalize">Missing</h3>
150-
<ul>
151-
${missingFiles.map(file => {
152-
const localization = file.localizations.find(
153-
localization => localization.lang === lang,
154-
)!
155-
return html`
156-
<li>
157-
${Link(links.source(file.source.path), collapsePath(file.source.path))}
158-
${CreateFileLink(links.create(localization.path), 'Create file')}
159-
</li>
160-
`
161-
})}
162-
</ul>`
163-
: ''
164-
}
165-
${
166-
missingFiles.length == 0 && outdatedFiles.length == 0
167-
? html`
133+
missingKeys.length > 0
134+
? html`${MissingKeysList(missingKeys)}`
135+
: html`
168136
<p>This translation is complete, amazing job! 🎉</p>
169137
`
170-
: ''
171138
}
172139
</details>
173140
`
174141
}
175142

176-
export const OutdatedFiles = (
177-
outdatedFiles: LunariaStatus,
178-
lang: string,
179-
lunaria: LunariaInstance,
143+
export const MissingKeysList = (missingKeys: string[]): string => {
144+
return html`<details>
145+
<summary>Show missing keys</summary>
146+
<ul>
147+
${missingKeys.map(key => html`<li>${key}</li>`)}
148+
</ul>
149+
</details>`
150+
}
151+
152+
export const ContentDetailsLinks = (
153+
githubEditLink: { text: string; url: string },
154+
githubHistoryUrl: string,
180155
): string => {
181156
return html`
182-
<h3 class="capitalize">Outdated</h3>
183-
<ul>
184-
${outdatedFiles.map(file => {
185-
const localization = file.localizations.find(localization => localization.lang === lang)!
186-
187-
const isMissingKeys =
188-
localization.status !== 'missing' &&
189-
'missingKeys' in localization &&
190-
localization.missingKeys.length > 0
191-
192-
return html`
193-
<li>
194-
${
195-
isMissingKeys
196-
? html`
197-
<details>
198-
<summary>${ContentDetailsLinks(file, lang, lunaria)}</summary>
199-
<h4>Missing keys</h4>
200-
<ul>
201-
${localization.missingKeys.map(key => html`<li>${(key as unknown as string[]).join('.')}</li>`)}
202-
</ul>
203-
</details>
204-
`
205-
: html` ${ContentDetailsLinks(file, lang, lunaria)} `
206-
}
207-
</li>
208-
`
209-
})}
210-
</ul>
157+
${Link(githubEditLink.url, githubEditLink.text)} |
158+
${Link(githubHistoryUrl, 'source change history')}
159+
`
160+
}
161+
162+
export const ProgressBar = (percentComplete: number): string => {
163+
let barClass = 'completed'
164+
165+
if (percentComplete > 99) {
166+
barClass = 'completed' // dark-green
167+
} else if (percentComplete > 90) {
168+
barClass = 'very-good' // green
169+
} else if (percentComplete > 75) {
170+
barClass = 'good' // orange
171+
} else if (percentComplete > 50) {
172+
barClass = 'help-needed' // red
173+
} else {
174+
barClass = 'basic' // dark-red
175+
}
176+
177+
return html`
178+
<div class="progress-bar-wrapper" aria-hidden="true">
179+
<div class="progress-bar ${barClass}" style="width:${percentComplete}%;"></div>
180+
</div>
211181
`
212182
}
213183

184+
export const Link = (href: string, text: string): string => {
185+
return html`<a href="${href}" target="_blank">${text}</a>`
186+
}
187+
188+
export const TitleParagraph = html`
189+
<p>
190+
If you're interested in helping us translate
191+
<a href="https://npmx.dev/">npmx.dev</a> into one of the languages listed below, you've come to
192+
the right place! This auto-updating page always lists all the content that could use your help
193+
right now.
194+
</p>
195+
<p>
196+
Before starting, please read our
197+
<a href="https://github.com/npmx-dev/npmx.dev/blob/main/CONTRIBUTING.md#localization-i18n"
198+
>localization (i18n) guide</a
199+
>
200+
to learn about our translation process and how you can get involved.
201+
</p>
202+
`
203+
204+
// Components from here are not used at the moment
205+
// Do not delete as we might use it if we split translations in multiple files for locale
214206
export const StatusByFile = (
215207
config: LunariaConfig,
216208
status: LunariaStatus,
@@ -265,7 +257,7 @@ export const TableContentStatus = (
265257
lunaria: LunariaInstance,
266258
fileType?: string,
267259
): string => {
268-
const localization = localizations.find(localization => localization.lang === lang)!
260+
const localization = localizations.find(l => l.lang === lang)!
269261
const isMissingKeys = 'missingKeys' in localization && localization.missingKeys.length > 0
270262
// For dictionary files, status is determined solely by key completion:
271263
// 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 = (
287279
return html`<td>${EmojiFileLink(link, status)}</td>`
288280
}
289281

290-
export const ContentDetailsLinks = (
291-
fileStatus: StatusEntry,
292-
lang: string,
293-
lunaria: LunariaInstance,
294-
): string => {
295-
const localization = fileStatus.localizations.find(localization => localization.lang === lang)!
296-
const isMissingKeys =
297-
localization.status !== 'missing' &&
298-
'missingKeys' in localization &&
299-
localization.missingKeys.length > 0
300-
301-
const links = lunaria.gitHostingLinks()
302-
303-
return html`
304-
${Link(links.source(fileStatus.source.path), collapsePath(fileStatus.source.path))}
305-
(${Link(
306-
links.source(localization.path),
307-
isMissingKeys ? 'incomplete translation' : 'outdated translation',
308-
)},
309-
${Link(
310-
links.history(
311-
fileStatus.source.path,
312-
'git' in localization
313-
? new Date(localization.git.latestTrackedCommit.date).toISOString()
314-
: undefined,
315-
),
316-
'source change history',
317-
)})
318-
`
319-
}
320-
321282
export const EmojiFileLink = (
322283
href: string | null,
323284
type: 'missing' | 'outdated' | 'up-to-date',
@@ -343,56 +304,10 @@ export const EmojiFileLink = (
343304
</span>`
344305
}
345306

346-
export const Link = (href: string, text: string): string => {
347-
return html`<a href="${href}">${text}</a>`
348-
}
349-
350307
export const CreateFileLink = (href: string, text: string): string => {
351308
return html`<a class="create-button" href="${href}">${text}</a>`
352309
}
353310

354-
export const ProgressBar = (
355-
total: number,
356-
outdated: number,
357-
missing: number,
358-
{ size = 20 }: { size?: number } = {},
359-
): string => {
360-
const outdatedSize = Math.round((outdated / total) * size)
361-
const missingSize = Math.round((missing / total) * size)
362-
const doneSize = size - outdatedSize - missingSize
363-
364-
const getBlocks = (size: number, type: 'missing' | 'outdated' | 'up-to-date') => {
365-
const items = []
366-
for (let i = 0; i < size; i++) {
367-
items.push(html`<div class="${type}-bar"></div>`)
368-
}
369-
return items
370-
}
371-
372-
return html`
373-
<div class="progress-bar" aria-hidden="true">
374-
${getBlocks(doneSize, 'up-to-date')} ${getBlocks(outdatedSize, 'outdated')}
375-
${getBlocks(missingSize, 'missing')}
376-
</div>
377-
`
378-
}
379-
380-
export const TitleParagraph = html`
381-
<p>
382-
If you're interested in helping us translate
383-
<a href="https://npmx.dev/">npmx.dev</a> into one of the languages listed below, you've come to
384-
the right place! This auto-updating page always lists all the content that could use your help
385-
right now.
386-
</p>
387-
<p>
388-
Before starting, please read our
389-
<a href="https://github.com/npmx-dev/npmx.dev/blob/main/CONTRIBUTING.md#localization-i18n"
390-
>localization (i18n) guide</a
391-
>
392-
to learn about our translation process and how you can get involved.
393-
</p>
394-
`
395-
396311
/**
397312
* Build an SVG file showing a summary of each language's translation progress.
398313
*/
@@ -421,11 +336,10 @@ function SvgLocaleSummary(
421336
{ label, lang }: Locale,
422337
): { svg: string; progress: number } {
423338
const missingFiles = status.filter(
424-
file =>
425-
file.localizations.find(localization => localization.lang === lang)?.status === 'missing',
339+
file => file.localizations.find(l => l.lang === lang)?.status === 'missing',
426340
)
427341
const outdatedFiles = status.filter(file => {
428-
const localization = file.localizations.find(localization => localization.lang === lang)
342+
const localization = file.localizations.find(l => l.lang === lang)
429343
if (!localization || localization.status === 'missing') {
430344
return false
431345
} else if (file.type === 'dictionary') {

0 commit comments

Comments
 (0)