mirror of
https://github.com/callumalpass/tasknotes.git
synced 2026-07-22 12:50:26 +00:00
Complete missing translations for all languages: - de, es, fr: All missing keys translated (100% coverage for existing keys) - ja, ru, zh: Missing keys translated and stale keys updated Add 159 new translation keys for previously hardcoded strings: - modals.deviceCode (12 keys) - modals.icsEventInfo (18 keys) - modals.timeblockInfo (23 keys) - modals.timeblockCreation (20 keys) - modals.icsNoteCreation (16 keys) - views.stats (25 keys) - views.agenda, views.notes, views.kanban (additional keys) - components.dateContextMenu (10 keys) - components.subgroupMenuBuilder (10 keys) - notices (23 keys) Update source files to use i18n: - 5 modal files - 5 view files - 3 component files - OAuthService, FilterBar Translation status: - de, es: 100% (1704/1704) - fr, ja, ru, zh: ~90% of new keys translated
71 lines
2.3 KiB
JavaScript
71 lines
2.3 KiB
JavaScript
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
const RESOURCES_DIR = path.resolve('src/i18n/resources');
|
|
const MANIFEST_PATH = path.resolve('i18n.manifest.json');
|
|
const STATE_PATH = path.resolve('i18n.state.json');
|
|
|
|
function flatten(tree, prefix = '') {
|
|
const entries = {};
|
|
for (const [key, value] of Object.entries(tree)) {
|
|
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
if (typeof value === 'string') {
|
|
entries[fullKey] = value;
|
|
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
Object.assign(entries, flatten(value, fullKey));
|
|
}
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
async function loadLocaleModule(locale) {
|
|
const filePath = path.join(RESOURCES_DIR, `${locale}.ts`);
|
|
let content = fs.readFileSync(filePath, 'utf8');
|
|
content = content.replace(/^import\s+.*?;$/gm, '');
|
|
content = content.replace(/export\s+type\s+.*?;$/gm, '');
|
|
content = content.replace(
|
|
new RegExp(`export\\s+const\\s+${locale}\\s*:\\s*\\w+\\s*=\\s*`, 'g'),
|
|
'export default '
|
|
);
|
|
content = content.replace(
|
|
new RegExp(`export\\s+const\\s+${locale}\\s*=\\s*`, 'g'),
|
|
'export default '
|
|
);
|
|
const tempPath = path.join(RESOURCES_DIR, `.${locale}.temp.mjs`);
|
|
fs.writeFileSync(tempPath, content);
|
|
try {
|
|
const absolutePath = path.resolve(tempPath);
|
|
const module = await import(`file://${absolutePath}?v=${Date.now()}`);
|
|
return module.default;
|
|
} finally {
|
|
if (fs.existsSync(tempPath)) {
|
|
fs.unlinkSync(tempPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function getLocaleMap(locale) {
|
|
const module = await loadLocaleModule(locale);
|
|
return flatten(module);
|
|
}
|
|
|
|
const locale = process.argv[2];
|
|
if (!locale) {
|
|
console.error('Usage: node export-missing.mjs <locale>');
|
|
process.exit(1);
|
|
}
|
|
|
|
const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
|
|
const state = JSON.parse(fs.readFileSync(STATE_PATH, 'utf8'));
|
|
const enMap = await getLocaleMap('en');
|
|
const targetMap = await getLocaleMap(locale);
|
|
|
|
const missing = [];
|
|
for (const key in manifest) {
|
|
const entry = state[locale]?.[key];
|
|
if (!entry || !entry.translation) {
|
|
missing.push({ key, value: enMap[key] });
|
|
}
|
|
}
|
|
|
|
console.log(JSON.stringify(missing, null, 2));
|