Seitse lokaliseerimist, kaks torujuhet ja miks ma üldse tõlkisin

← Tagasi blogisse

22. mai 20268 min lugemine

Seitse lokaliseerimist, kaks torujuhet ja miks ma üldse tõlkisin

Kuidas üks inimene hoiab ära seitse lokaliseerimist ilma iga JSON-faili käsitsi muutmata ja millised tööturud tegid selle ehitamise väärt.

i18nportfoliofrontend

Üks ingliskeelne portfell on kehtiv valik. Sama kehtib seitsme lokaliseerimise saatmisest, kui olete üks inimene, kes hooldab hoidlat: see ei ole "tasuta", kuid see on loetav, kui käsitlete keelt kui toote ulatust, mitte kui märkeruutu, ja automatiseerite igavaid osi.

See postitus on pikk versioon: millised keeled, miks ma need valisin ja täpselt mida kood teeb (packages/types, packages/i18n, scripts/translate.mjs, apps/web/scripts/translate-blog.mjs).

Miks need keeled (strateegia, mitte meelevaldsus)

Ma ei valinud lokaliseerimisi, sest mall saadetakse nendega. Ma ei optimeerinud "enim rääkijaid maailmas" kui ühte arvu. Ma alustasin töögeograafiast: ma vaatasin riike ja piirkondi, kus arendajaid palkatakse (kaugtöö-sõbralikud tööandjad, tugevad kodumaised tehnoloogiamarknad, EL-i keskused) ja küsisin, millised keeled ma saan realistlikult saata, et need lugejad saaksid saidi kasutada selles keeles, mida nad tegelikult loevad, kui see on oluline, mitte ainult inglise keeles. See on toote valik selle kohta, kes võib seda portfelli hinnata, mitte kaunistus.

Inglise keel kui vaikimisi on endiselt läbiräägitamatu: see on tehnoloogia jaoks lai ühine nimetaja. Kuus mitte-ingliskeelset lokaliseerimist selles hoidlas on portugali, hispaania, saksa, poola, hollandi ja eesti, loetletud LOCALE_CONFIG-is packages/types/src/index.ts. Koos ingliskeelse keelega on see seitse pinnalokaliseerimist.

Kavatsus on lihtne: kui keegi, kes võib mind palkama või koostööd tegema, satub siia Brasiiliast või Portugalist, Hispaaniast või Ladina-Ameerikast, Saksamaalt, Austriast või Šveitsist, Poolast, Hollandist või Eestist, tahan ma, et nad loeksid keeles, mis vastab sellele, kuidas nad tegelikult loevad, kui see on oluline, samal ajal aktsepteerides, et üks es katab palju riike ja üks pt katab kahte väga erinevat turgu. See on kompromiss, mitte teadmatus. Üks lokaliseerimiskood ei saa katta kõiki kultuurilisi registreid nendest keeltest, ja ma ei pretendeeri, et see saab.

Kas iga lause kõlab nagu inimtõlkija oleks tund aega sellele pühendanud? Ei. Masintõlge ja LLM-iga abistatud partiid on osa virnast. Lubadus on kitsam ja minu arvates ausam: teadlik katvus, git-is ülevaatavad erinevused ja koopia, mis ei ole vaikimisi ingliskeelne lugejatele, kes eelistavad teist emakeelt.

Mida "seitse lokaliseerimist" tähendab selles koodibaasis

Kõik, mis vajab teadmist "millised keeled on olemas", peaks tuletama LOCALE_CONFIG: võtmed en, pt, es, de, pl, nl, et, iga nimi, bcp47, ja franc kood keele tuvastamise abiks. i18n pakett ekspordib selle konfiguratsiooni ja loob locales kui Object.keys(LOCALE_CONFIG); vt packages/i18n/src/index.ts.

Jooksmisaegsed sõnastikud on staatilised JSON-failid lokalisatsiooni kohta packages/i18n/src/messages/, imporditud väikese importija kaudu, et pakkujad saaksid iga faili lahendada. htmlLangFromLocale abifunktsioon kaardistab lokalisatsiooni BCP 47 märgiks <html lang> jaoks. See on "ainus tõe allikas": tüübid + JSON-failid + Next rakendus kõik rivistuvad samale lokalisatsioonide loendile.

Kui te lisate lokalisatsiooni, laiendate LOCALE_CONFIG, lisate uue JSON-faili ja ühendate importija, seejärel käitate tõlke torujuhet. Pole peidetud jooksmisaegset loendit, mis võib kõrvale kalduda.

// packages/types/src/index.ts
export const LOCALE_CONFIG = {
  en: { name: "English",    bcp47: "en", franc: "eng" },
  pt: { name: "Portugês",  bcp47: "pt", franc: "por" },
  es: { name: "Español",    bcp47: "es", franc: "spa" },
  de: { name: "Deutsch",    bcp47: "de", franc: "deu" },
  pl: { name: "Polski",     bcp47: "pl", franc: "pol" },
  nl: { name: "Nederlands", bcp47: "nl", franc: "nld" },
  et: { name: "Eesti",      bcp47: "et", franc: "est" },
} as const;

export type Locale = keyof typeof LOCALE_CONFIG;

Torujuhe A: UI stringid, en.json, lukufail, hübriidne MT/LLM

Autoriteetne UI koopia elab packages/i18n/src/messages/en.json. Teised keeled on genereeritud; projekti reegel on selgesõnaline: redigeeri ainult ingliskeelset, seejärel käita pnpm translate hoidla juurest, mis käivitab scripts/translate.mjs keskkonnaga, mis on laaditud apps/web/.env.local. Vt juur package.json skript.

Inkrementaalne tõlge ja lukufail

translate.mjs on pikk põhjusel. See hooldab scripts/translate.lock.json: lokalisatsiooni kohta hashid osadest ingliskeelsest allikast, et kui te muudate üht osa en.json-st, ei saada kogu sõnastikku pimedalt ümber saata API-le. See on oluline, kui en.json kasvab (nt lehtede, projektide, kogemuste kohta) ja te hoolite kulu ja korratavuse kohta.

Skript ka eemaldab võtmeid, mis ingliskeelsest keelest kadusid, nii et lokalisatsiooni failid ei akumuleeri orvosi harusid, ja see võib eemaldada teadaolevalt tõlkimatu terminid (omastavad nimed, tehnoloogiamärgid) enne taotlust, seejärel ühendab need tagasi; vt DO_NOT_TRANSLATE komplekt ja seotud abifunktsioonid failis.

Hübriidtõlge: LibreTranslate vs Groq

Vahendite valik oli tingitud kulust ja sobivusest. LibreTranslate on ise-hostitav Dockeriga ilma tokeni kohta kuluta, mis teeb selle õigeks valikuks lühikeste, informatiivsete stringide jaoks nagu nav, meta ja errors; need võtmed ei vaja LLM-taseme nüanssi, ja maksmine tegelikult DeepL või Google Translate'i eest lisaks ilma kvaliteeti lisamata. Groq käsitleb LLM-i teed, sest selle järeldus on kiire ja odav partiitööks võrreldes OpenAI või Anthropiciga sama mahu korral; vaikimisi TRANSLATE_MODEL on väike mudel (llama-4-scout), mis on valitud spetsiaalselt selleks, et hoida käitamise maksumust madalana, mitte sama mudel, mida kasutatakse vestluses.

Skript ei saada kõike Groqile. CONFIG objekt kodeerib tegeliku poliitika:

  • Lugu võtmed, mis vastavad mustritele groqKeyPatterns-is (nt about.sections.*.body, home.hero.subheadline, projects.items.*.problem, experience.items.*.impact) läbivad Groqi iga lokalisatsiooni kohta shouldUseGroqForKey kaudu. Töötajate tiitlid (experience.items.*.role) on alati Groq, sest puhas MT sageli tagastab ingliskeelsed tiitlid muutumatult.
  • Lühikesed või informatiivsemad nimeruumid on kallutatud LibreTranslate-i suunas libreKeyPrefixes kaudu (nt meta, nav, errors, experience.labels), LIBRETRANSLATE_URL vaikimisi kohalikule Docker-sõbralikule hostile, kui te seda ei ületa.

Seega: iga lokalisatsioon saab LLM-kvaliteedi lugude väljade kohta; LibreTranslate käsitleb informatiivseid võtmeid, kus masintõlge on piisavalt täpne. See on kulu ja kvaliteedi kompromiss, mis on sisse ehitatud koodi, mitte kommentaar README-s.

// scripts/translate.mjs
function shouldUseGroqForKey(localeCode, key) {
  return (
    matchesDotPattern(key, "experience.items.*.role") ||
    CONFIG.groqKeyPatterns.some((pat) => matchesDotPattern(key, pat))
  );
}

Keskkond ja toimingud, mida te tegelikult puudutate

Kaks Groqi võtit on tahtlikult eraldi. GROQ_API_KEY elab tootmises ja teenib portfelli vestlust käitusajal; tõlke partiid, mis suurendavad tokeni kasutamist, sööksid sama kiiruspiirangu sisse ja mõjutaksid tegelikke kasutajaid. TRANSLATE_GROQ_API_KEY on erinev võti, mida kasutatakse ainult kohalikes partiides, oma kvotaga. Varundamine GROQ_API_KEY-le eksisteerib mugavuse huvides, kui spetsiaalse võtit pole konfigureeritud, mitte kui kavandatud seadistus.

TRANSLATE_MODEL valib mudeli partiitõlke jaoks (vaikimisi meta-llama/llama-4-scout-17b-16e-instruct skripti päises), sõltumatult GROQ_MODEL-ist, mida kasutab vestlus. Vaikimisi on tahtlikult väike, kiire mudel, et hoida käitamise maksumust madalana. Seal on nuppe partii suuruse, 429 korduvate päringute ja logimise jaoks; lugege faili ülaosa kommentaarplokki scripts/translate.mjs enne häälestamist.

API kasutamine Groqi (ja LibreTranslate'i hostimine, kui te seda kohalikult käitate) on reaalne raha. Võit on siin kontroll ja automatiseerimine, mitte maagia. Lukufail on see, mis muudab selle juhitavaks: osad, mis ingliskeelses keeles ei muutunud, ei lähe järgmisel käitusajal ühelegi API-le.

Torujuhe B: Blogipostitused, ingliskeelne MDX, seejärel Groq-only partiid

Blogipostitusi ei tõlgita sama hübriidrouteriga. pnpm translate:blog käivitab apps/web/scripts/translate-blog.mjs, mis loeb apps/web/content/blog/en/*.mdx ja iga sihtlokalisatsiooni kohta TARGET_LOCALES-is (pt, es, de, pl, nl, et), helistab Groqile range viibaga: säilitage YAML-eelne struktuur, hoidke date ja hero identsena ingliskeelse allikaga, tõlkige pealkiri, kirjeldus, sildid ja keha, ärge segage URL-e. Väljundfailid maanduvad apps/web/content/blog/<locale>/.

Pole LibreTranslate-i teed selles skriptis; blogi tõlge on LLM-partii faili kohta, koos --locale, --file, --force, ja --delay-iga, et vähendada kiiruspiirangu valu. Käitusajal laadib Next lihtsalt MDX-i marsruudi lokalisatsiooni kohta; pole Groqi kõnet, kui külastaja avab blogi lehe.

Käitusajaline: marsruudid, sõnastikud ja blogi teed

Next.js rakendus kasutab [locale] segmenti apps/web/app/[locale]/. Serverikomponendid kutsuvad normalizeLocale-i parameetri kohta, nii et tundmatud väärtused langevad tagasi en, seejärel getDictionary(locale) laadib vastava JSON-mooduli packages/i18n. Sellepärast näeb iga lehe mall struktuurilt ühesugune välja: nad kõik loevad koopia sõnastikust, mitte kõvasti kodeeritud ingliskeelseid stringe JSX-is (harvade eranditega, mida linter märgistab).

Blogi marsruudid ühendavad lokalisatsiooni slugiga: avaldatud postitusi loetakse content/blog/<locale>/<slug>.mdx, kui slug on lubatud publish.json. Seega "toetab seitset lokaliseerimist" ei ole ainult tõlke skripte; see on ka staatilised marsruudid + staatilised sisu failid, mis peavad eksisteerima iga keele kohta, mida te hoolite.

Lubadus kvaliteedi kohta on kitsam kui kirjanduslik viimistlus: järjepidevad torujuhed, ülevaatavad väljundid git-is ja aus prioriteet: pt/es lugude väljad Groqi kaudu, kõik muu MT kaudu, kui võti vastab rollimustrile. See ei ole tõrke režiim; see on avaldatud ulatus.

Kui te ehitate sama asja

Alustage inimestest, keda soovite lugeda ja millised tööturud on teie jaoks olulised, mitte "mitmest lipust". Lokaliseerimiste loend on tooteotsus; käsitlege seda kui tooteotsust.

Alustage väikesest. Üks ekstra lokalisatsioon (tõenäoliselt see, kus teil on professionaalsed ühendused või aktiivselt töö otsimine) on piisav, et valideerida torujuhe. Lisage LOCALE_CONFIG kirje, lisage JSON-fail, käitage pnpm translate. Kui väljundi kvaliteet on vastuvõetav selle lokalisatsiooni jaoks, on teil korratav tee. Te ei vaja kuut mitte-ingliskeelset lokaliseerimist, et tõestada arhitektuuri toimimist.

Otsustage oma hübriidpoliitika varakult. LibreTranslate on kiire ja tasuta, kui te seda kohalikult käitate; Groq lisab kvaliteeti lugude väljadele, kuid maksab tokenite ja lisab latentsust. Kui te ainult saatate ühe või kahe mitte-ingliskeelse lokalisatsiooni, on Groq-only kogu sõnastiku jaoks lihtsam kui tõlke loogika ülesehitamine shouldUseGroqForKey-s. Hübriidne keerukus translate.mjs-is tasub end ainult ära, kui teil on piisavalt võtmeid ja piisavalt lokaliseerimisi, et MT kvaliteedi varieerumine tegelikult mõjutab neid.

Hoidke ingliskeelne allikas kanooniline ja väljundi erinevused ülevaatavad. Lukufail scripts/translate.lock.json-is ei ole valikuline tseremoonia; see on see, mis võimaldab teil käivitada pnpm translate ilma uuesti kulutamata tokenitele osadele, mis ingliskeelses keeles ei muutunud. Iga torujuhe, mis tõlgib kogu sõnastiku iga käitusajal, tabab kulu ja kiiruspiirangu probleeme enne, kui teie portfellil on teine lugeja. Kohustage genereeritud lokalisatsioonifaile, et saate ülevaadata iga tõlke muutust tavalises tõmbeavalduses.

Seejärel rakendage üht ingliskeelset allikat, genereeritud satelliitfaile ja skripte, mida saate käivitada CI-s või kohalikult, et teie portfell jääb tooteks, mida saate arendada, mitte hunnikuks käsitsi redigeeritud koopiaid.

Kokkuvõte

Rakendus ei ole keeruline osa. LOCALE_CONFIG, kaks partiiskripti, lukufail: ükski korralik insener ei saa seda nädalavahetusel üles ehitada. Raskem osa on otsustada ette, et keel on toote kitsendus, mitte funktsioon, mida te lisate, kui ülejäänu on "valmis".

Kui te käsitlete i18n-i kui kaunistust, siis saatate selle täpselt üks kord ja puudutate seda enam kunagi. Kui te käsitlete seda kui ulatust, automatiseerimisega, ülevaatavate erinevustega ja ausa nimekirjaga sellest, mida see ei kata, ja see jääb hooldatavaks, ja te kontrollite, mida teie portfell edastab lugejatele, keda te ehk kunagi ingliskeelses keeles ei kohtagi.

See on tegelik panus siin: mitte see, et iga lause kõlab nagu inimtõlkija oleks tund aega sellele pühendanud, vaid et sait töötab someone jaoks Varssavis või Tallinnas, kes eelistab mitte lugeda ingliskeeles, kui see on oluline. Kas see panus tasub end ära, on TBD.