mirror of
https://github.com/screentinker/screentinker.git
synced 2026-05-15 07:32:23 -06:00
Session 1 of 2 of the i18n rollout. - Split i18n module into per-language files under frontend/js/i18n/ so a translator can edit one language without touching the others. - Add Portuguese (pt) and seed Hindi (hi). Hindi is intentionally a skeleton -- 0 keys, full English fallback -- because we have an active Indian user and would rather ship "no Hindi" than ship machine-quality Hindi that could read as unprofessional or get formality/gender register wrong. - 183 keys, 100% parity across en/es/fr/de/pt; native review still recommended before publicizing as "fully supported". - Add t(key, vars) variable substitution and tn(keyBase, n, vars) plural helper for _one/_other key pairs. - setLanguage() now triggers a CustomEvent + HashChangeEvent so the existing hash router naturally re-renders the current view, plus a subscriber pattern for nav labels rendered once outside the router. - Wire t() into 3 high-traffic views end-to-end: dashboard, login, content-library. Sidebar nav labels in app.js update on language change. - The remaining 16 views still ship with hardcoded English; they will be wired in session 2. The t() lookup is robust against unwired views, so the dashboard works in 5 languages while clicking into e.g. Schedule still shows English. No regressions. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
80 lines
2.7 KiB
JavaScript
80 lines
2.7 KiB
JavaScript
// Lightweight i18n loader. Each language is its own file under ./i18n/ so a
|
|
// translator can edit one file without touching the others. English is the
|
|
// canonical source — every other locale falls back to en for any missing key.
|
|
import en from './i18n/en.js';
|
|
import es from './i18n/es.js';
|
|
import fr from './i18n/fr.js';
|
|
import de from './i18n/de.js';
|
|
import pt from './i18n/pt.js';
|
|
import hi from './i18n/hi.js';
|
|
|
|
const fallback = en;
|
|
const registry = { en, es, fr, de, pt, hi };
|
|
|
|
let currentLang = localStorage.getItem('rd_lang') || navigator.language?.split('-')[0] || 'en';
|
|
if (!registry[currentLang]) currentLang = 'en';
|
|
|
|
function lookup(key) {
|
|
return registry[currentLang]?.[key] ?? fallback[key] ?? key;
|
|
}
|
|
|
|
// Replace {name} placeholders in a string with the matching property of vars.
|
|
// Unknown placeholders pass through unchanged so a missing var is visible
|
|
// during development rather than silently dropped.
|
|
function format(s, vars) {
|
|
if (!vars) return s;
|
|
return s.replace(/\{(\w+)\}/g, (m, k) => (k in vars ? String(vars[k]) : m));
|
|
}
|
|
|
|
export function t(key, vars) {
|
|
return format(lookup(key), vars);
|
|
}
|
|
|
|
// Plural helper: looks up `${keyBase}_one` for n===1 else `${keyBase}_other`,
|
|
// auto-injects `{n}` into vars. Use for any string that varies on a count.
|
|
export function tn(keyBase, n, vars = {}) {
|
|
const key = keyBase + (n === 1 ? '_one' : '_other');
|
|
return format(lookup(key), { n, ...vars });
|
|
}
|
|
|
|
const subscribers = new Set();
|
|
|
|
// Views and the navbar subscribe so they can rebuild themselves on language
|
|
// change. Also fires a `language-changed` CustomEvent and a hashchange so the
|
|
// existing hash router naturally re-renders the current view.
|
|
export function subscribe(fn) {
|
|
subscribers.add(fn);
|
|
return () => subscribers.delete(fn);
|
|
}
|
|
|
|
export function setLanguage(lang) {
|
|
if (!registry[lang] || lang === currentLang) return;
|
|
currentLang = lang;
|
|
localStorage.setItem('rd_lang', lang);
|
|
document.documentElement.setAttribute('lang', lang);
|
|
subscribers.forEach((fn) => { try { fn(lang); } catch {} });
|
|
window.dispatchEvent(new CustomEvent('language-changed', { detail: { lang } }));
|
|
window.dispatchEvent(new HashChangeEvent('hashchange'));
|
|
}
|
|
|
|
export function getLanguage() {
|
|
return currentLang;
|
|
}
|
|
|
|
export function getAvailableLanguages() {
|
|
return [
|
|
{ code: 'en', name: 'English' },
|
|
{ code: 'es', name: 'Español' },
|
|
{ code: 'fr', name: 'Français' },
|
|
{ code: 'de', name: 'Deutsch' },
|
|
{ code: 'pt', name: 'Português' },
|
|
{ code: 'hi', name: 'हिन्दी' },
|
|
];
|
|
}
|
|
|
|
// Apply the persisted language to <html lang=...> on first load so screen
|
|
// readers and CSS :lang() selectors are accurate before any user interaction.
|
|
if (typeof document !== 'undefined') {
|
|
document.documentElement.setAttribute('lang', currentLang);
|
|
}
|