mirror of
https://github.com/penpot/penpot.git
synced 2026-09-30 07:46:16 +00:00
✨ Add japanese translations (#11922)
This commit is contained in:
parent
eec06a5987
commit
9d08e26cb3
@ -35,7 +35,7 @@ high-coverage support reference, never the base.
|
|||||||
plus a `#_{:clj-kondo/ignore [:penpot/tr-dynamic]}` on the call site.
|
plus a `#_{:clj-kondo/ignore [:penpot/tr-dynamic]}` on the call site.
|
||||||
- New entries take `#:` refs from `en`; copy `#, unused`, never `#, fuzzy`.
|
- 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.
|
- `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.
|
- `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).
|
- 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);
|
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 = [];
|
const found = [];
|
||||||
for (const m of text.matchAll(PUNCT_RE)) {
|
for (const m of text.matchAll(PUNCT_RE)) {
|
||||||
const punct = m[0][0];
|
const punct = m[0][0];
|
||||||
@ -502,6 +520,8 @@ function checkPunctuation(text, brands) {
|
|||||||
for (const m of text.matchAll(CAMEL_RE)) {
|
for (const m of text.matchAll(CAMEL_RE)) {
|
||||||
const frag = text.slice(Math.max(0, m.index - 30), m.index + 32);
|
const frag = text.slice(Math.max(0, m.index - 30), m.index + 32);
|
||||||
if (brands.some((mk) => frag.includes(mk))) continue;
|
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 });
|
found.push({ what: m[0], frag });
|
||||||
}
|
}
|
||||||
for (const m of text.matchAll(PH_GLUED_RE)) {
|
for (const m of text.matchAll(PH_GLUED_RE)) {
|
||||||
@ -581,6 +601,8 @@ async function check(locale, words) {
|
|||||||
const entriesEn = dataEn.translations[""];
|
const entriesEn = dataEn.translations[""];
|
||||||
const entriesEs = dataEs ? dataEs.translations[""] : {};
|
const entriesEs = dataEs ? dataEs.translations[""] : {};
|
||||||
const brands = [...GENERIC_BRANDS, ...(words?.brands ?? [])];
|
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 errors = [];
|
||||||
const warnings = [];
|
const warnings = [];
|
||||||
|
|
||||||
@ -609,8 +631,11 @@ async function check(locale, words) {
|
|||||||
|
|
||||||
texts.forEach((text, i) => {
|
texts.forEach((text, i) => {
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
const textEn = textsEn[i] ?? "";
|
// A single-form locale's only string must match the source's plural
|
||||||
const textEs = textsEs[i] ?? "";
|
// 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);
|
const refs = refToks(textEn, textEs);
|
||||||
if (textEn) {
|
if (textEn) {
|
||||||
const unused = (e.comments?.flag ?? "")
|
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(
|
errors.push(
|
||||||
`${msgid}: glued punctuation ${JSON.stringify(t.what)} ...${t.frag}...`,
|
`${msgid}: glued punctuation ${JSON.stringify(t.what)} ...${t.frag}...`,
|
||||||
);
|
);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user