fb5 UI-TW: the machine-translation mark — _meta.machineTranslated: true, a new optional manifest field of a language pack (docs/PACKS.md): shared/i18nPacks.js langFields reads it (true or false), shared/packs.js normalizeManifest warns about a value that is not a boolean (read as false; the pack still loads) and packIndexEntry passes it into the index entry like complete (only when true), so the client's registry has it from the index (langMetaOf → registerLangs → langInfo) or from the pack's own _meta without one; 设置 shows a note under the language switch while the CURRENT language's pack has it (ui/lang.js machineTranslationNote, msgid 「当前语言的界面文字为机器翻译,可能不够准确,欢迎在 GitHub 上指正。」 — English in en.json, 目前語言的介面文字為機器翻譯… in zh-TW.json; ja / ko leave it to the integrator); tests: test/i18n-packs.test.js (the field from the manifest through the index to langInfo, the warning, the shipped zh-TW manifest, the note shown / hidden by language), test/ui/i18n.e2e.test.js (the shipped packs' own UI strings read from their files — zh-TW now 開始 — and the 设置 note, none in Chinese); docs/I18N.md says how the zh-TW pack is made (OpenCC twp, the official TW terms, a manual pass; a later msgid the same way) and that the ja / ko strings are machine translations open to corrections (edit public/i18n/<code>.json, node tools/i18n.mjs check <code> --strict)
This commit is contained in:
1 parent
6613c3d0fb
commit
c11ba03da4
11 files changed
+173
-38
No files matched your search
+6
-2
@@ -43,7 +43,7 @@ export const DEFAULT_LANG = SOURCE_LANG;
|
||||
/** The source language: built in, no pack, no catalog. */
|
||||
const SOURCE_META = Object.freeze({
|
||||
code: SOURCE_LANG, name: '中文', englishName: 'Chinese (Simplified)', authors: [], credits: '', version: '', app: '', // i18n-ignore: its own name
|
||||
base: null, fallback: [], complete: true, numberUnits: ['万', '亿'], builtin: true, // i18n-ignore: the Chinese number units
|
||||
base: null, fallback: [], complete: true, machineTranslated: false, numberUnits: ['万', '亿'], builtin: true, // i18n-ignore: the Chinese number units
|
||||
});
|
||||
|
||||
/** @type {Map<string, LangMeta>} code → metadata: the source, the packs of the index, languages given messages */
|
||||
@@ -109,7 +109,11 @@ export function getLangs() {
|
||||
return [registry.get(SOURCE_LANG), ...rest];
|
||||
}
|
||||
|
||||
/** The metadata of a known language, or null. @param {string} [lang] */
|
||||
/**
|
||||
* The metadata of a known language (its names, chain fields, `complete`, `machineTranslated` …: what registerLangs or its
|
||||
* pack's `_meta` gave), or null.
|
||||
* @param {string} [lang]
|
||||
*/
|
||||
export const langInfo = (lang = current) => registry.get(String(lang)) || null;
|
||||
|
||||
/**
|
||||
|
||||
+8
-2
@@ -20,6 +20,8 @@
|
||||
// pack for it — pt-BR → pt; never the Chinese source, so zh-TW has none); null switches it off
|
||||
// fallback packs tried after the base, before the Chinese msgid (["en"]: untranslated strings show English)
|
||||
// complete true: `node tools/i18n.mjs check` requires every msgid (English declares it)
|
||||
// machineTranslated true: the UI strings are machine translations (or a machine conversion, zh-TW); the 设置 dialog
|
||||
// says so under the language switch while the language is in use (ui/lang.js machineTranslationNote)
|
||||
// numberUnits two units for numbers counted in 10⁴ / 10⁸ steps (Chinese 万 / 亿; e.g. a Japanese pack ["万", "億"]);
|
||||
// without them large numbers use K / M / B (ui/gameLogic/format.js fmtNum)
|
||||
// The common fields (id, version, app, name, englishName, authors, credits, license) are shared/packs.js's.
|
||||
@@ -84,7 +86,8 @@ export function langList(v) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{ lang: string, base: string|null|undefined, fallback: string[], complete: boolean, numberUnits: string[]|null }} LangFields
|
||||
* @typedef {{ lang: string, base: string|null|undefined, fallback: string[], complete: boolean, machineTranslated: boolean,
|
||||
* numberUnits: string[]|null }} LangFields
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -101,7 +104,10 @@ export function langFields(code, m) {
|
||||
else if (typeof o.base === 'string') base = canonicalLang(o.base);
|
||||
if (base === SOURCE_LANG || base === code) base = null;
|
||||
const units = Array.isArray(o.numberUnits) && o.numberUnits.length === 2 && o.numberUnits.every((u) => str(u, 8)) ? o.numberUnits.map((u) => str(u, 8)) : null;
|
||||
return { lang: code, base, fallback: langList(o.fallback).filter((c) => c !== code), complete: o.complete === true, numberUnits: units };
|
||||
return {
|
||||
lang: code, base, fallback: langList(o.fallback).filter((c) => c !== code), complete: o.complete === true,
|
||||
machineTranslated: o.machineTranslated === true, numberUnits: units,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+9
-3
@@ -23,13 +23,14 @@
|
||||
// englishName its English name
|
||||
// authors a string or a list; credits free text; license an SPDX id or free text
|
||||
// files a folder pack: role → path inside the folder (type "lang": { "ui": "ui.json", "data": "data.json" })
|
||||
// … the type's own fields (type "lang": lang, base, fallback, complete, numberUnits — shared/i18nPacks.js)
|
||||
// … the type's own fields (type "lang": lang, base, fallback, complete, machineTranslated, numberUnits —
|
||||
// shared/i18nPacks.js; a machineTranslated that is not a boolean is warned about and reads as false)
|
||||
//
|
||||
// Index (GET /packs/index.json — the server's live list; `node tools/packs.mjs index` writes packs/index.json for a
|
||||
// static host): { version: 1, app: "<the server's app version>", packs: [entry …] } — the packs of supported types,
|
||||
// sorted by type then id. An entry: id, type, name, englishName, version?, app?, compatible, authors?, credits?,
|
||||
// license?, files (role → URL on this server) and the type's fields; a language entry: lang, base?, fallback,
|
||||
// complete?, numberUnits?, strings (how many translations its UI file holds).
|
||||
// complete?, machineTranslated?, numberUnits?, strings (how many translations its UI file holds).
|
||||
|
||||
import { APP_VERSION } from './constants.js';
|
||||
import { SOURCE_LANG, canonicalLang, isLangCode, languageName, langFields } from './i18nPacks.js';
|
||||
@@ -134,7 +135,8 @@ export function appVersionMatches(range, version = APP_VERSION) {
|
||||
/**
|
||||
* @typedef {{ id: string, type: string, name: string, englishName: string, version: string, app: string,
|
||||
* compatible: boolean, authors: string[], credits: string, license: string, files: Record<string, string>,
|
||||
* lang?: string, base?: string|null, fallback?: string[], complete?: boolean, numberUnits?: string[]|null }} PackManifest
|
||||
* lang?: string, base?: string|null, fallback?: string[], complete?: boolean, machineTranslated?: boolean,
|
||||
* numberUnits?: string[]|null }} PackManifest
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -178,6 +180,9 @@ export function normalizeManifest(raw, ctx) {
|
||||
else {
|
||||
if (m.lang !== undefined && !isLangCode(m.lang) && !ctx.lang) warnings.push(`"lang": "${m.lang}" read as ${code}`);
|
||||
Object.assign(out, langFields(code, m));
|
||||
// anything but true reads as false: say so when another value was meant
|
||||
const mt = m.machineTranslated;
|
||||
if (mt !== undefined && typeof mt !== 'boolean') warnings.push(`"machineTranslated": ${String(JSON.stringify(mt)).slice(0, 40)} is not true or false (read as false)`);
|
||||
out.name ||= languageName(code);
|
||||
out.englishName ||= languageName(code, 'en');
|
||||
}
|
||||
@@ -220,6 +225,7 @@ export function packIndexEntry(manifest, extra) {
|
||||
if (manifest.base !== undefined) e.base = manifest.base;
|
||||
e.fallback = manifest.fallback || [];
|
||||
if (manifest.complete) e.complete = true;
|
||||
if (manifest.machineTranslated) e.machineTranslated = true;
|
||||
if (manifest.numberUnits) e.numberUnits = manifest.numberUnits;
|
||||
e.strings = extra.strings ?? 0;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user