Repository navigation
Expand file tree
/
Copy pathi18n.ts
More file actions
103 lines (92 loc) · 3.85 KB
/
Copy pathi18n.ts
File metadata and controls
103 lines (92 loc) · 3.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import en from './locales/en.json';
import toolsEn from './locales/tools/en.json';
import site from './config/site.json';
import { CASE_FORMAT, FREE_PLAN } from './lib/seo/site-text';
import { type AppLanguage } from './lib/languages';
type Catalogs = [{ default: object }, { default: object }];
// English ships in the entry: it is the default and every other language's
// fallback. Spanish and Hungarian are their own chunks, fetched only when chosen.
const CATALOGS: Record<Exclude<AppLanguage, 'en'>, () => Promise<Catalogs>> = {
es: () => Promise.all([import('./locales/es.json'), import('./locales/tools/es.json')]),
hu: () => Promise.all([import('./locales/hu.json'), import('./locales/tools/hu.json')]),
};
// Bundled resources make init synchronous; the returned promise cannot reject.
void i18n.use(initReactI18next).init({
// The free /tools pages keep their strings in their own namespace.
resources: {
en: { translation: en, tools: toolsEn },
},
ns: ['translation', 'tools'],
defaultNS: 'translation',
lng: 'en',
fallbackLng: 'en',
// Every string can name the product with {{product}}, {{brand}} and {{host}},
// state the trial length and price with {{trialDays}} and {{proMonthly}}, and
// quote the English case format and plan sentences as {{caseFormat}} and {{freePlan}},
// all from site.json, so every language states the same facts as the English pages.
interpolation: {
escapeValue: false,
defaultVariables: {
product: site.product,
brand: site.brand,
host: new URL(site.origin).host,
trialDays: site.pricing.trialDays,
proMonthly: site.pricing.proMonthly,
caseFormat: CASE_FORMAT,
freePlan: FREE_PLAN,
},
},
});
/** Whether the language's strings are in i18next, fetching them first if needed. */
async function loadCatalogs(language: AppLanguage): Promise<boolean> {
if (language === 'en' || i18n.hasResourceBundle(language, 'translation')) return true;
try {
const [app, tools] = await CATALOGS[language]();
i18n.addResourceBundle(language, 'translation', app.default);
i18n.addResourceBundle(language, 'tools', tools.default);
return true;
} catch (error) {
// Offline before this language was ever cached: stay on the current one.
console.warn(`[i18n] Could not load the ${language} strings:`, error);
return false;
}
}
/**
* Fetches every language's strings without switching to any of them. The
* flowchart recognises starter text saved in any language, so its page waits
* for this.
*/
export async function loadAllAppLanguages(): Promise<void> {
await Promise.all((Object.keys(CATALOGS) as AppLanguage[]).map(loadCatalogs));
}
let requestedLanguage: AppLanguage = 'en';
let pendingLanguage: Promise<void> = Promise.resolve();
export function applyAppLanguage(language: AppLanguage): Promise<void> {
requestedLanguage = language;
pendingLanguage = (async () => {
// A later choice wins, even if this one's strings arrive after it.
if (!(await loadCatalogs(language)) || requestedLanguage !== language) return;
await i18n.changeLanguage(language);
if (typeof document === 'undefined') return;
document.documentElement.lang = language;
})();
return pendingLanguage;
}
// A language chosen offline, before its strings were ever cached, stays on the
// current one; try again when the connection returns.
if (typeof window !== 'undefined') {
window.addEventListener('online', () => {
if (i18n.language !== requestedLanguage) void applyAppLanguage(requestedLanguage);
});
}
/**
* Settles once the last requested language is showing (or has failed to load).
* The first render waits for it, so a Spanish or Hungarian visitor never sees
* English first.
*/
export function appLanguageReady(): Promise<void> {
return pendingLanguage;
}
export default i18n;