Files
kapteins-daagbok/scripts/translate-locales.mjs
T
elpatron 3749f87c1d Add Scandinavian i18n (da/sv/nb) via DeepL pipeline.
Integrate new locale bundles, language cycling in the UI, SEO hreflang tags, and localized beta flyer HTML variants with scripts for batch translation and key validation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-31 15:53:43 +02:00

100 lines
2.7 KiB
JavaScript

#!/usr/bin/env node
/**
* Translate i18n locale JSON from German master via DeepL.
*
* Usage:
* node scripts/translate-locales.mjs [--lang da,sv,nb] [--source client/src/i18n/locales/de.json]
*/
import { readFile, writeFile } from 'node:fs/promises'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import {
flattenTranslation,
loadEnvKey,
translateTexts,
unflattenTranslation
} from './lib/deepl-translate.mjs'
const __dirname = dirname(fileURLToPath(import.meta.url))
const repoRoot = resolve(__dirname, '..')
const defaultSource = resolve(repoRoot, 'client/src/i18n/locales/de.json')
const TARGETS = {
da: 'DA',
sv: 'SV',
nb: 'NB'
}
/** Keys whose values stay identical to source (language names, brand). */
const COPY_AS_IS_PREFIXES = ['languages.', 'app.name']
function parseArgs(argv) {
let langs = Object.keys(TARGETS)
let sourcePath = defaultSource
for (let i = 2; i < argv.length; i++) {
if (argv[i] === '--lang' && argv[i + 1]) {
langs = argv[++i].split(',').map((l) => l.trim())
} else if (argv[i] === '--source' && argv[i + 1]) {
sourcePath = resolve(repoRoot, argv[++i])
}
}
return { langs, sourcePath }
}
function shouldCopyAsIs(path) {
return COPY_AS_IS_PREFIXES.some((prefix) => path === prefix || path.startsWith(prefix))
}
async function translateLocale(sourceJson, langCode, apiKey) {
const entries = flattenTranslation(sourceJson.translation)
const toTranslate = []
const indices = []
entries.forEach(([path, value], index) => {
if (shouldCopyAsIs(path)) return
toTranslate.push(value)
indices.push(index)
})
console.log(`Translating ${toTranslate.length} strings to ${langCode.toUpperCase()}`)
const translated = await translateTexts(toTranslate, TARGETS[langCode], {
sourceLang: 'DE',
apiKey
})
const resultEntries = [...entries]
indices.forEach((entryIndex, i) => {
resultEntries[entryIndex][1] = translated[i]
})
return { translation: unflattenTranslation(resultEntries) }
}
async function main() {
const { langs, sourcePath } = parseArgs(process.argv)
const apiKey = loadEnvKey()
const sourceRaw = await readFile(sourcePath, 'utf8')
const sourceJson = JSON.parse(sourceRaw)
for (const lang of langs) {
if (!TARGETS[lang]) {
console.error(`Unknown language: ${lang}`)
process.exit(1)
}
const outPath = resolve(repoRoot, `client/src/i18n/locales/${lang}.json`)
console.log(`\n${lang}.json`)
const translated = await translateLocale(sourceJson, lang, apiKey)
await writeFile(outPath, `${JSON.stringify(translated, null, 2)}\n`, 'utf8')
console.log(`Wrote ${outPath}`)
}
}
main().catch((err) => {
console.error(err.message ?? err)
process.exit(1)
})