/
githubmirror
/
obsidian-importer
Обзор
Документация
Войти
/
githubmirror
/
obsidian-importer
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
scripts/locales.ts
118 строк
4 KB
Steph Ango
Complete localization (#624)
12 авг 2026, 01:20
Не верифицирован
12 авг 2026, 01:20
658bd86
Код
Авторство
О чём код?
/** * Keeps the translation files and the bundled locale data in step with * `src/i18n/en.ts`. * * npm run locales rewrite locale/*.txt and src/i18n/locales.ts * npm run locales -- check report what is out of date, change nothing * * Rewriting a translation file preserves what has been translated, refreshes * the `original=` line under every key, adds keys that are new and drops keys * that are gone. The check mode is what the test suite runs, so a string added * without regenerating fails rather than quietly shipping untranslated. */ import { readdirSync, readFileSync, writeFileSync } from 'node:fs'; import * as path from 'node:path'; import { fileURLToPath } from 'node:url'; import { en } from '../src/i18n/en'; import { Bundle, flatten, parseLocale, stringifyLocale } from '../src/i18n/util'; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const localeDir = path.join(root, 'locale'); const bundlePath = path.join(root, 'src', 'i18n', 'locales.ts'); const check = process.argv.slice(2).includes('check'); const english = flatten(en); const keys = Object.keys(english); /** Every `<lang>.txt` beside en.txt, in the order they are listed. */ function translationFiles(): string[] { return readdirSync(localeDir) .filter(name => name.endsWith('.txt') && name !== 'en.txt') .map(name => name.slice(0, -'.txt'.length)) .sort(); } /** A single-quoted literal, since the generated file is linted like any other. */ function quote(text: string): string { const escaped = text .replace(/[\\']/g, c => '\\' + c) .replace(/\n/g, '\\n') .replace(/\r/g, '\\r'); return `'${escaped}'`; } /** * Keys once, then a row per language holding the translations in that order. * * Spelling every key out in every language cost 600KB of the bundle. Gzip does * not recover it either: deflate looks back 32KB and a language is nearly 50KB, * so a key's previous copy is out of reach by the time it comes round again. */ function generateBundle(bundles: Record<string, Bundle>): string { const keyList = keys.map(key => `\t${quote(key)},\n`).join(''); const languages = Object.keys(bundles).map(language => { const row = keys.map(key => bundles[language][key] === undefined ? '0' : quote(bundles[language][key])); return `\t${quote(language)}: [${row.join(',')}],`; }); return '// Generated by `npm run locales` from locale/*.txt. Do not edit.\n' + '// A key left untranslated is 0 here, so it falls back to English.\n' + '\n' + `export const keys: string[] = [\n${keyList}];\n` + '\n' + `export const values: Record<string, ReadonlyArray<string | 0>> = {\n${languages.join('\n')}\n};\n`; } const outdated: string[] = []; function put(file: string, contents: string): void { let existing: string | null = null; try { existing = readFileSync(file, 'utf8'); } catch { existing = null; } if (existing === contents) return; if (check) { outdated.push(path.relative(root, file)); return; } writeFileSync(file, contents); console.log(`Wrote ${path.relative(root, file)}`); } const bundles: Record<string, Bundle> = {}; put(path.join(localeDir, 'en.txt'), stringifyLocale(english, {})); for (const language of translationFiles()) { const file = path.join(localeDir, `${language}.txt`); const translated = parseLocale(readFileSync(file, 'utf8')); // Only keys the English table still has: a translation left behind by a // removed string would otherwise sit in the bundle forever. const kept: Bundle = {}; for (const key of keys) { if (translated[key] !== undefined) kept[key] = translated[key]; } bundles[language] = kept; put(file, stringifyLocale(english, kept)); const missing = keys.length - Object.keys(kept).length; console.log(`${language}: ${Object.keys(kept).length}/${keys.length} translated${missing ? `, ${missing} to go` : ''}`); } put(bundlePath, generateBundle(bundles)); if (check && outdated.length > 0) { console.error(`Out of date with src/i18n/en.ts: ${outdated.join(', ')}\nRun \`npm run locales\`.`); process.exit(1); }