-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathi18n.js
More file actions
153 lines (140 loc) · 5.07 KB
/
Copy pathi18n.js
File metadata and controls
153 lines (140 loc) · 5.07 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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// Shared catalog loader for extension pages, service workers, and content scripts.
(function (root) {
'use strict';
const supportedLanguages = ['browser', 'en', 'de', 'cs', 'es', 'fr', 'hu', 'it', 'nl', 'pl'];
let catalog = {};
let languagePreference = 'browser';
let catalogLocale = 'en';
let loadPromise = null;
function getBrowserAPI() {
return typeof browser !== 'undefined' ? browser : chrome;
}
function normalizeLanguage(value) {
return supportedLanguages.includes(value) ? value : 'browser';
}
function getBrowserLocale() {
try {
const api = getBrowserAPI();
if (api.i18n && typeof api.i18n.getUILanguage === 'function') {
return api.i18n.getUILanguage();
}
} catch (error) {
// Fall through to navigator.language.
}
return typeof navigator !== 'undefined' ? navigator.language : 'en';
}
function localeCandidates(preference) {
const requested = preference === 'browser' ? getBrowserLocale() : preference;
const normalized = String(requested || 'en').replace('_', '-').toLowerCase();
const base = normalized.split('-')[0];
const candidates = [];
if (supportedLanguages.includes(normalized)) candidates.push(normalized);
if (supportedLanguages.includes(base)) candidates.push(base);
if (!candidates.includes('en')) candidates.push('en');
return candidates;
}
async function fetchCatalog(locale) {
const api = getBrowserAPI();
const url = api.runtime.getURL(`_locales/${locale}/messages.json`);
const response = await fetch(url);
if (!response.ok) throw new Error(`Could not load locale catalog ${locale}`);
return response.json();
}
async function loadCatalog(preference) {
const normalizedPreference = normalizeLanguage(preference);
languagePreference = normalizedPreference;
loadPromise = (async () => {
for (const locale of localeCandidates(normalizedPreference)) {
try {
catalog = await fetchCatalog(locale);
catalogLocale = locale;
return catalog;
} catch (error) {
// Try the next locale, then use the native API as a final fallback.
}
}
catalog = {};
catalogLocale = 'en';
return catalog;
})();
return loadPromise;
}
async function initializeI18n(preference) {
if (preference === undefined) {
try {
const api = getBrowserAPI();
const data = await api.storage.local.get('settings');
preference = data && data.settings && data.settings.language;
} catch (error) {
preference = undefined;
}
}
return loadCatalog(preference || 'browser');
}
function substitute(message, substitutions) {
if (substitutions === undefined || substitutions === null) return message;
const values = Array.isArray(substitutions) ? substitutions : [substitutions];
let index = 0;
const replacements = {};
return message.replace(/\$([A-Z0-9_]+)\$/g, (match, name) => {
if (!Object.prototype.hasOwnProperty.call(replacements, name)) {
replacements[name] = values[index++];
}
return replacements[name] === undefined ? match : String(replacements[name]);
});
}
function i18nMessage(key, substitutions) {
const entry = catalog[key];
if (entry && typeof entry.message === 'string') {
return substitute(entry.message, substitutions);
}
try {
const api = getBrowserAPI();
const value = api.i18n && api.i18n.getMessage(key, substitutions);
return value || key;
} catch (error) {
return key;
}
}
function localizeDocument(rootElement) {
if (typeof document === 'undefined') return;
const root = rootElement || document;
root.querySelectorAll('[data-i18n]').forEach(element => {
element.textContent = i18nMessage(element.dataset.i18n);
});
root.querySelectorAll('[data-i18n-placeholder]').forEach(element => {
element.placeholder = i18nMessage(element.dataset.i18nPlaceholder);
});
root.querySelectorAll('[data-i18n-title]').forEach(element => {
element.title = i18nMessage(element.dataset.i18nTitle);
});
root.querySelectorAll('[data-i18n-aria-label]').forEach(element => {
element.setAttribute('aria-label', i18nMessage(element.dataset.i18nAriaLabel));
});
if (document.documentElement) {
document.documentElement.lang = catalogLocale;
}
}
root.HaiiloI18n = {
supportedLanguages,
initializeI18n,
loadCatalog,
i18nMessage,
localizeDocument,
getLanguagePreference: () => languagePreference,
getCatalogLocale: () => catalogLocale,
getLoadPromise: () => loadPromise
};
root.i18nMessage = i18nMessage;
root.localizeDocument = localizeDocument;
root.initializeI18n = initializeI18n;
root.loadI18nCatalog = loadCatalog;
if (typeof document !== 'undefined') {
const initializeDocument = () => initializeI18n().then(() => localizeDocument());
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeDocument, { once: true });
} else {
initializeDocument();
}
}
})(globalThis);