forked from ChelseaKR/nearmiss
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathi18n.js
More file actions
139 lines (128 loc) · 4.95 KB
/
Copy pathi18n.js
File metadata and controls
139 lines (128 loc) · 4.95 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
/* nearmiss shared web i18n loader — used by the national studio and retained
* source-only methods/submission prototypes.
*
* The web UI's translations are single-sourced from the gettext PO catalogs:
* tools/po2json.py compiles the `web.*` msgids into committed shared source
* catalogs. The production site builder emits only the `web.coverage.*`
* namespace; local prototypes read the full source catalogs. There is no
* hand-maintained translation table in the JS.
*
* Keys in the JSON are the full msgids (e.g. "web.app.title"). Each page creates
* a namespaced view — NearmissI18n.create("web.app.") — so its call sites keep
* using short keys, t("title"), unchanged. English is always loaded as the
* fallback, so a key missing from a locale renders in English, never blank.
*/
(function () {
"use strict";
var FALLBACK = "en";
var SUPPORTED = { en: true, es: true };
function localeRoot() {
var script = document.currentScript;
if (!script || !script.src) {
// jsdom and other outside-only harnesses do not expose currentScript.
// Fall back to the declared loader element rather than the page route.
var scripts = document.getElementsByTagName("script");
for (var index = scripts.length - 1; index >= 0; index -= 1) {
if (/(^|\/)i18n\.js(?:[?#].*)?$/.test(scripts[index].src || "")) {
script = scripts[index];
break;
}
}
}
try {
var scriptUrl =
script && script.src
? script.src
: new URL("/web/i18n.js", window.location.href).href;
return new URL("locales/", scriptUrl).href;
} catch (_error) {
return "/web/locales/";
}
}
var LOCALE_ROOT = localeRoot();
function isSupported(lang) {
return Object.prototype.hasOwnProperty.call(SUPPORTED, lang);
}
function create(namespace) {
// Array-backed catalogs avoid treating locale names or catalog msgids as
// JavaScript object properties. Both arrive through fetched JSON, so keeping
// them as values also makes prototype-key injection impossible.
var catalogs = [];
var current = FALLBACK;
function catalogFor(lang) {
for (var index = 0; index < catalogs.length; index += 1) {
if (catalogs[index].lang === lang) return catalogs[index].messages;
}
return null;
}
function messageFrom(messages, key) {
if (!messages) return null;
for (var index = 0; index < messages.length; index += 1) {
if (messages[index].key === key) return messages[index].value;
}
return null;
}
function ingest(lang, raw) {
if (!isSupported(lang)) return;
var messages = [];
Object.keys(raw || {}).forEach(function (fullKey) {
if (fullKey.indexOf(namespace) === 0 && typeof raw[fullKey] === "string") {
messages.push({ key: fullKey.slice(namespace.length), value: raw[fullKey] });
}
});
catalogs = catalogs.filter(function (catalog) {
return catalog.lang !== lang;
});
catalogs.push({ lang: lang, messages: messages });
}
return {
// Fetch and cache locales/<lang>.json. Resolves even on failure (an empty
// catalog), so the caller transparently falls back to English.
load: function (lang) {
if (!isSupported(lang) || catalogFor(lang)) return Promise.resolve();
return fetch(LOCALE_ROOT + lang + ".json")
.then(function (r) {
if (!r.ok) throw new Error("HTTP " + r.status);
return r.json();
})
.then(function (raw) {
ingest(lang, raw);
})
.catch(function () {
ingest(lang, {});
});
},
setLang: function (lang) {
current = isSupported(lang) ? lang : FALLBACK;
},
lang: function () {
return current;
},
loaded: function (lang) {
return Boolean(catalogFor(lang));
},
// Look up a short key in the current locale, then English, then echo the
// key so a stray call is visible rather than silently blank.
t: function (key) {
var active = messageFrom(catalogFor(current), key);
if (active !== null) return active;
var fallback = messageFrom(catalogFor(FALLBACK), key);
return fallback !== null ? fallback : key;
},
};
}
// Read a UI-language request from ?lang=xx, but only select a locale this
// deployment actually ships. This keeps English fallback content labeled
// lang="en" instead of, for example, lang="ar" when no Arabic catalog exists.
function langFromQuery(fallback) {
var safeFallback = isSupported(fallback) ? fallback : FALLBACK;
try {
var value = new URLSearchParams(window.location.search).get("lang");
if (value && isSupported(value)) return value;
} catch (e) {
/* no URLSearchParams — use the fallback */
}
return safeFallback;
}
window.NearmissI18n = { create: create, langFromQuery: langFromQuery, FALLBACK: FALLBACK };
})();