mirror of
https://github.com/penpot/penpot.git
synced 2026-09-28 09:32:57 +08:00
✨ Add japanese translations (#11922)
This commit is contained in:
@@ -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).
|
||||
|
||||
|
||||
@@ -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}...`,
|
||||
);
|
||||
|
||||
+8437
-94
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user