mirror of
https://github.com/TautvydasDerzinskas/Thingport.git
synced 2026-10-22 06:32:27 -05:00
fix(i18n): require each locale's plural forms in i18n:check
Lithuanian lacked _few forms, so counts like 2 fell back to English. Add them, fix _other wording for 10+, and update stale docs and comments.
This commit is contained in:
1 parent
10451a9f11
commit
96300510b4
5 files changed
+45
-15
No files matched your search
+6
-4
@@ -31,16 +31,18 @@ To improve an existing language, edit its file the same way; the commit type is
|
||||
|
||||
## Plurals
|
||||
|
||||
Text that depends on a number has one key per plural form, ending in `_one`, `_few`, `_many` or `_other`:
|
||||
Text that depends on a number has one key per plural form, ending in `_zero`, `_one`, `_two`, `_few`, `_many`
|
||||
or `_other`:
|
||||
|
||||
```json
|
||||
"minutesAgo_one": "{{count}} minute ago",
|
||||
"minutesAgo_other": "{{count}} minutes ago"
|
||||
```
|
||||
|
||||
Use the forms your language needs, which aren't always English's: Lithuanian, for example, adds `_few`. Check your
|
||||
language in the [Unicode plural rules](https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html)
|
||||
and add or drop forms to match.
|
||||
Use the forms your language needs, which aren't always English's: Lithuanian, for example, adds `_few`, and Arabic
|
||||
and Welsh also use `_zero` and `_two`. Check your language in the
|
||||
[Unicode plural rules](https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html) and add or
|
||||
drop forms to match; `i18n:check` below lists any form your language is missing.
|
||||
|
||||
## Check your translation
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Lists each locale's keys missing from, or no longer in, en.json: `npm run i18n:check [code]`.
|
||||
// Plural forms (key_one, key_few, ...) count as one key, since languages need different ones.
|
||||
// A plural key (key_one, key_other, ...) needs every form the locale uses for whole-number counts.
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
|
||||
const dir = new URL("../src/i18n/locales/", import.meta.url);
|
||||
@@ -7,12 +7,25 @@ const PLURAL = /_(zero|one|two|few|many|other)$/;
|
||||
|
||||
function keys(node, prefix = "") {
|
||||
return Object.entries(node).flatMap(([key, value]) =>
|
||||
value && typeof value === "object" ? keys(value, `${prefix}${key}.`) : [`${prefix}${key}`.replace(PLURAL, "")],
|
||||
value && typeof value === "object" ? keys(value, `${prefix}${key}.`) : [`${prefix}${key}`],
|
||||
);
|
||||
}
|
||||
|
||||
const read = (code) => new Set(keys(JSON.parse(readFileSync(new URL(`${code}.json`, dir), "utf8"))));
|
||||
const en = read("en");
|
||||
const read = (code) => keys(JSON.parse(readFileSync(new URL(`${code}.json`, dir), "utf8")));
|
||||
|
||||
// Fractional-only forms (Lithuanian `many`) are left out: counts here are whole numbers.
|
||||
function pluralForms(code) {
|
||||
const rules = new Intl.PluralRules(code);
|
||||
return new Set(Array.from({ length: 1000 }, (_, n) => rules.select(n)));
|
||||
}
|
||||
|
||||
const plurals = new Set();
|
||||
const plain = [];
|
||||
for (const key of read("en")) {
|
||||
if (PLURAL.test(key)) plurals.add(key.replace(PLURAL, ""));
|
||||
else plain.push(key);
|
||||
}
|
||||
|
||||
const only = process.argv[2];
|
||||
const codes = readdirSync(dir)
|
||||
.filter((file) => file.endsWith(".json") && file !== "en.json")
|
||||
@@ -25,7 +38,10 @@ if (only && codes.length === 0) {
|
||||
}
|
||||
|
||||
for (const code of codes) {
|
||||
const locale = read(code);
|
||||
const forms = [...pluralForms(code)];
|
||||
const en = new Set([...plain, ...[...plurals].flatMap((base) => forms.map((form) => `${base}_${form}`))]);
|
||||
// Extra plural forms a translator added (such as `_many`) aren't stale, just unused for whole numbers.
|
||||
const locale = new Set(read(code).filter((key) => en.has(key) || !plurals.has(key.replace(PLURAL, ""))));
|
||||
const missing = [...en].filter((key) => !locale.has(key));
|
||||
const stale = [...locale].filter((key) => !en.has(key));
|
||||
const done = Math.floor(((en.size - missing.length) / en.size) * 100);
|
||||
|
||||
@@ -287,10 +287,13 @@
|
||||
"viewSource": "Žiūrėti šaltinį",
|
||||
"justNow": "Ką tik",
|
||||
"minutesAgo_one": "prieš {{count}} minutę",
|
||||
"minutesAgo_few": "prieš {{count}} minutes",
|
||||
"minutesAgo_other": "prieš {{count}} minučių",
|
||||
"hoursAgo_one": "prieš {{count}} valandą",
|
||||
"hoursAgo_few": "prieš {{count}} valandas",
|
||||
"hoursAgo_other": "prieš {{count}} valandų",
|
||||
"daysAgo_one": "prieš {{count}} dieną",
|
||||
"daysAgo_few": "prieš {{count}} dienas",
|
||||
"daysAgo_other": "prieš {{count}} dienų",
|
||||
"clearAll": "Išvalyti viską",
|
||||
"clearAllConfirm": "Ištrinti visus pranešimus? To nebus galima atšaukti.",
|
||||
@@ -320,7 +323,8 @@
|
||||
"sectionCollections": "Kolekcijos",
|
||||
"sectionTags": "Žymos",
|
||||
"modelCount_one": "{{count}} modelis",
|
||||
"modelCount_other": "{{count}} modeliai",
|
||||
"modelCount_few": "{{count}} modeliai",
|
||||
"modelCount_other": "{{count}} modelių",
|
||||
"sectionRecent": "Neseniai",
|
||||
"sectionCommands": "Komandos",
|
||||
"open": "Atidaryti",
|
||||
@@ -1085,14 +1089,19 @@
|
||||
"approx": "≈ {{value}}",
|
||||
"units": {
|
||||
"minutes_one": "{{count}} minutė",
|
||||
"minutes_few": "{{count}} minutės",
|
||||
"minutes_other": "{{count}} minučių",
|
||||
"hours_one": "{{count}} valanda",
|
||||
"hours_few": "{{count}} valandos",
|
||||
"hours_other": "{{count}} valandų",
|
||||
"days_one": "{{count}} diena",
|
||||
"days_few": "{{count}} dienos",
|
||||
"days_other": "{{count}} dienų",
|
||||
"weeks_one": "{{count}} savaitė",
|
||||
"weeks_few": "{{count}} savaitės",
|
||||
"weeks_other": "{{count}} savaičių",
|
||||
"months_one": "{{count}} mėnuo",
|
||||
"months_few": "{{count}} mėnesiai",
|
||||
"months_other": "{{count}} mėnesių"
|
||||
},
|
||||
"save": "Išsaugoti",
|
||||
@@ -2822,7 +2831,8 @@
|
||||
"card": {
|
||||
"empty": "Kol kas nėra modelių",
|
||||
"itemCount_one": "{{count}} modelis",
|
||||
"itemCount_other": "{{count}} modeliai"
|
||||
"itemCount_few": "{{count}} modeliai",
|
||||
"itemCount_other": "{{count}} modelių"
|
||||
},
|
||||
"form": {
|
||||
"createTitle": "Nauja kolekcija",
|
||||
@@ -2998,9 +3008,11 @@
|
||||
"downloadZip": {
|
||||
"loading": "Skaičiuojama…",
|
||||
"summary_one": "{{count}} modelis (~{{size}})",
|
||||
"summary_other": "{{count}} modeliai (~{{size}})",
|
||||
"summary_few": "{{count}} modeliai (~{{size}})",
|
||||
"summary_other": "{{count}} modelių (~{{size}})",
|
||||
"summaryNoSize_one": "{{count}} modelis",
|
||||
"summaryNoSize_other": "{{count}} modeliai",
|
||||
"summaryNoSize_few": "{{count}} modeliai",
|
||||
"summaryNoSize_other": "{{count}} modelių",
|
||||
"summaryFailed": "Nepavyko apskaičiuoti atsisiuntimo dydžio. Bandykite dar kartą.",
|
||||
"empty": "Nerasta modelių failų atsisiuntimui.",
|
||||
"downloading": "Atsisiunčiama…",
|
||||
|
||||
@@ -32,7 +32,7 @@ type Props = {
|
||||
/** GitHub's contribution graph: a column per week, a square per day, darker the busier the day. */
|
||||
export default function Heatmap({ range, days, selectedDay, today, onSelectDay }: Props) {
|
||||
const { t, i18n } = useTranslation("app");
|
||||
// Monday-first in Lithuanian, as its calendars are; Sunday-first otherwise, as GitHub's is.
|
||||
// Follows the locale's calendar: Sunday-first where it starts the week then (as GitHub does), Monday otherwise.
|
||||
const weekStart = weekStartFor(i18n.language);
|
||||
const weeks = React.useMemo(() => buildWeeks(range, weekStart), [range, weekStart]);
|
||||
const months = React.useMemo(() => monthStarts(weeks), [weeks]);
|
||||
|
||||
@@ -291,7 +291,7 @@ export const DOCS = [
|
||||
group: "Features",
|
||||
title: "Themes and languages",
|
||||
nav: "Themes and languages",
|
||||
description: "Light, dark or system theme, in English or Lithuanian.",
|
||||
description: "Light, dark or system theme, in any language Thingport is translated into.",
|
||||
},
|
||||
{
|
||||
slug: "guides/system-health",
|
||||
|
||||
Reference in new issue
Block a user