✨ Add japanese translations (#11922)

This commit is contained in:
Alonso Torres
2026-09-25 14:57:56 +02:00
committed by GitHub
parent eec06a5987
commit 9d08e26cb3
3 changed files with 8467 additions and 99 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ high-coverage support reference, never the base.
plus a `#_{:clj-kondo/ignore [:penpot/tr-dynamic]}` on the call site.
- New entries take `#:` refs from `en`; copy `#, unused`, never `#, fuzzy`.
- `en` keys with empty `msgstr` (or `#, fuzzy` + empty): translate from `es`/source context, never leave empty.
- Entries whose `en` uses `msgid_plural` need `msgstr[0]`/`msgstr[1]` (header: `nplurals=2; plural=n != 1`). Keep the `msgid_plural` line: a singular `msgstr` on a plural key silently breaks count selection at runtime (the app build reads 1-elem `msgstr` as singular).
- Entries whose `en` uses `msgid_plural` need `msgstr[0]`/`msgstr[1]` (header: `nplurals=2; plural=n != 1`). Keep the `msgid_plural` line: a singular `msgstr` on a plural key silently breaks count selection at runtime (the app build reads 1-elem `msgstr` as singular). Exception: single-form locales (`nplurals=1`, e.g. `jpn_JP`, `ko`) keep ONE `msgstr[0]` that includes the count `%s` (build emits a plain string, runtime formats it with the count for every n); `check` compares it against the `en` plural form.
- `sync` re-adds `#, fuzzy` on EVERY run for entries fuzzy in `en`; re-strip after the last sync, never before it.
- Preserve verbatim: `%s`/`%d`, `{var}`/`{{...}}`, markdown `[text](%s)`, HTML tags, `\n` positions, brand names (Penpot), key names (Ctrl/Shift/Alt), technical terms (SVG, CSS, HSV, RGB).
+29 -4
View File
@@ -486,7 +486,25 @@ function cleanContext(frag) {
return !/https?:|www\.|@|\|target:|\.mcp\.json/.test(frag);
}
function checkPunctuation(text, brands) {
// Latin identifier (e.g. `tokenName`) that contains the char at `index`.
function latinWordAt(text, index) {
const isWord = (c) => /[A-Za-zÀ-ÿ0-9_]/u.test(c);
let start = index;
let end = index;
while (start > 0 && isWord(text[start - 1])) start--;
while (end < text.length && isWord(text[end])) end++;
return text.slice(start, end);
}
// Number of plural forms declared by a PO `Plural-Forms` header.
function parseNplurals(header) {
const m = /nplurals\s*=\s*(\d+)/.exec(header ?? "");
return m ? Number(m[1]) : 2;
}
// `source` is the English text: camelCase identifiers copied verbatim
// from it (code names, not words glued together) are not reported.
function checkPunctuation(text, brands, source = "") {
const found = [];
for (const m of text.matchAll(PUNCT_RE)) {
const punct = m[0][0];
@@ -502,6 +520,8 @@ function checkPunctuation(text, brands) {
for (const m of text.matchAll(CAMEL_RE)) {
const frag = text.slice(Math.max(0, m.index - 30), m.index + 32);
if (brands.some((mk) => frag.includes(mk))) continue;
const word = latinWordAt(text, m.index);
if (source && new RegExp(`\\b${word}\\b`, "u").test(source)) continue;
found.push({ what: m[0], frag });
}
for (const m of text.matchAll(PH_GLUED_RE)) {
@@ -581,6 +601,8 @@ async function check(locale, words) {
const entriesEn = dataEn.translations[""];
const entriesEs = dataEs ? dataEs.translations[""] : {};
const brands = [...GENERIC_BRANDS, ...(words?.brands ?? [])];
// Single-form locales (ja, ko, zh…) render every count with msgstr[0].
const singleForm = parseNplurals(data.headers?.["Plural-Forms"]) === 1;
const errors = [];
const warnings = [];
@@ -609,8 +631,11 @@ async function check(locale, words) {
texts.forEach((text, i) => {
if (!text) return;
const textEn = textsEn[i] ?? "";
const textEs = textsEs[i] ?? "";
// A single-form locale's only string must match the source's plural
// form (it carries the count placeholder), not the singular one.
const j = singleForm && eEn?.msgid_plural ? textsEn.length - 1 : i;
const textEn = textsEn[j] ?? "";
const textEs = textsEs[j] ?? "";
const refs = refToks(textEn, textEs);
if (textEn) {
const unused = (e.comments?.flag ?? "")
@@ -629,7 +654,7 @@ async function check(locale, words) {
}
}
}
for (const t of checkPunctuation(text, brands)) {
for (const t of checkPunctuation(text, brands, textEn)) {
errors.push(
`${msgid}: glued punctuation ${JSON.stringify(t.what)} ...${t.frag}...`,
);
File diff suppressed because it is too large Load Diff