source: Klonkt/src/services/i18n.js@ 03fa548

main
Last change on this file since 03fa548 was 03fa548, checked in by roboburr <roboburr@…>, 3 months ago

feat(i18n): interface language NL/EN/DE with visitor switcher (phase 1: chrome)

  • i18n.js (t helper + nl/en/de dictionary + resolveLang: session → browser → nl).
  • render.js passes t/lang/langs to all views; /lang/:code sets the language in the session.
  • Language switcher (select) in the footer; chrome translated: topnav (Archive/Login/Account/Admin/Logout/…), pill (Agenda/Solo/Circle/Timeline/Grid), post nav (Newer/Older), footer.
  • Content (posts/site name/bio) stays in the author's language. Admin + deeper page strings follow in a next phase.

Co-Authored-By: Claude <noreply@…>

  • Property mode set to 100644
File size: 4.0 KB
Line 
1// i18n — eenvoudige interface-vertaling (UI-strings), bezoeker-instelbaar.
2//
3// Taalkeuze: req.session.lang (gezet via /lang/:code) → anders browser-taal
4// (Accept-Language) → anders 'nl'. De helper t(lang, key, vars) zoekt de string op
5// in DICT[lang], valt terug op 'nl', dan op de key zelf. Alleen INTERFACE-teksten;
6// door gebruikers geschreven content (posts, sitenaam, bio) wordt niet vertaald.
7
8export const SUPPORTED = ['nl', 'en', 'de'];
9export const LANG_NAMES = { nl: 'Nederlands', en: 'English', de: 'Deutsch' };
10
11const DICT = {
12 nl: {
13 'nav.back_to_site': '← Terug naar site',
14 'nav.home': 'Home',
15 'nav.archive': 'Archief',
16 'nav.search': 'Zoeken',
17 'nav.theme': 'Thema wisselen',
18 'nav.install': 'App installeren',
19 'nav.login': 'Inloggen',
20 'nav.logout': 'Uitloggen',
21 'nav.admin': 'Beheer',
22 'nav.account': 'Account',
23 'nav.profile': 'Profiel',
24 'nav.favorites': 'Favorieten',
25 'nav.new_post': 'Nieuwe post',
26 'nav.language': 'Taal',
27 'switch.agenda': 'Agenda',
28 'switch.solo': 'Solo',
29 'switch.circle': 'Cirkel',
30 'switch.timeline': 'Tijdlijn',
31 'switch.grid': 'Grid',
32 'postnav.newer': 'Nieuwer',
33 'postnav.older': 'Ouder',
34 'postnav.newest': 'Nieuwste post',
35 'postnav.oldest': 'Oudste post',
36 'footer.subscribe_cta': 'Blijf op de hoogte',
37 'footer.subscribe': 'Inschrijven',
38 'footer.install': 'Installeer app',
39 'common.email_placeholder': 'jouw@email.nl',
40 },
41 en: {
42 'nav.back_to_site': '← Back to site',
43 'nav.home': 'Home',
44 'nav.archive': 'Archive',
45 'nav.search': 'Search',
46 'nav.theme': 'Toggle theme',
47 'nav.install': 'Install app',
48 'nav.login': 'Log in',
49 'nav.logout': 'Log out',
50 'nav.admin': 'Admin',
51 'nav.account': 'Account',
52 'nav.profile': 'Profile',
53 'nav.favorites': 'Favorites',
54 'nav.new_post': 'New post',
55 'nav.language': 'Language',
56 'switch.agenda': 'Agenda',
57 'switch.solo': 'Solo',
58 'switch.circle': 'Circle',
59 'switch.timeline': 'Timeline',
60 'switch.grid': 'Grid',
61 'postnav.newer': 'Newer',
62 'postnav.older': 'Older',
63 'postnav.newest': 'Newest post',
64 'postnav.oldest': 'Oldest post',
65 'footer.subscribe_cta': 'Stay in the loop',
66 'footer.subscribe': 'Subscribe',
67 'footer.install': 'Install app',
68 'common.email_placeholder': 'you@email.com',
69 },
70 de: {
71 'nav.back_to_site': '← Zurück zur Seite',
72 'nav.home': 'Start',
73 'nav.archive': 'Archiv',
74 'nav.search': 'Suche',
75 'nav.theme': 'Thema wechseln',
76 'nav.install': 'App installieren',
77 'nav.login': 'Anmelden',
78 'nav.logout': 'Abmelden',
79 'nav.admin': 'Verwaltung',
80 'nav.account': 'Konto',
81 'nav.profile': 'Profil',
82 'nav.favorites': 'Favoriten',
83 'nav.new_post': 'Neuer Beitrag',
84 'nav.language': 'Sprache',
85 'switch.agenda': 'Termine',
86 'switch.solo': 'Solo',
87 'switch.circle': 'Kreis',
88 'switch.timeline': 'Zeitleiste',
89 'switch.grid': 'Raster',
90 'postnav.newer': 'Neuer',
91 'postnav.older': 'Älter',
92 'postnav.newest': 'Neuester Beitrag',
93 'postnav.oldest': 'Ältester Beitrag',
94 'footer.subscribe_cta': 'Bleib auf dem Laufenden',
95 'footer.subscribe': 'Abonnieren',
96 'footer.install': 'App installieren',
97 'common.email_placeholder': 'du@email.de',
98 },
99};
100
101export function t(lang, key, vars) {
102 const l = SUPPORTED.includes(lang) ? lang : 'nl';
103 let s = (DICT[l] && DICT[l][key]);
104 if (s === undefined) s = (DICT.nl[key] !== undefined ? DICT.nl[key] : key);
105 if (vars) for (const k in vars) s = s.replace(new RegExp('\\{' + k + '\\}', 'g'), vars[k]);
106 return s;
107}
108
109// Bepaal de taal voor dit request: sessie-keuze → browser-taal → nl.
110export function resolveLang(req) {
111 const s = req && req.session && req.session.lang;
112 if (s && SUPPORTED.includes(s)) return s;
113 const al = ((req && req.headers && req.headers['accept-language']) || '').toLowerCase();
114 const first = al.split(',')[0].trim().slice(0, 2);
115 if (SUPPORTED.includes(first)) return first;
116 return 'nl';
117}
Note: See TracBrowser for help on using the repository browser.