-
Notifications
You must be signed in to change notification settings - Fork 1
/
i18n.js
170 lines (144 loc) · 4.02 KB
/
i18n.js
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright © 2021 fvtt-shared-library Rui Pinheiro
'use strict';
import { IS_UNITTEST, PACKAGE_ID } from "../consts.js";
import { game_settings_get } from "./polyfill.js";
import { Log } from "./log.js";
// We want to load the EN language by default, in order to use it for polyfill while i18n hasn't loaded yet
// The import/fetch below will allow Rollup, with rollup-plugin-json and rollup-plugin-jscc, to directly include the JSON contents into the build artifact
// but also still allow libWrapper to work fine without the rollup step.
/*#if _ROLLUP
import en_json from '../../lang/en.json';
const i18nLangs = $_I18N_LANGS;
const langBaseUrl = (!import.meta?.url?.endsWith(`dist/${PACKAGE_ID}.js`)) ? './lang' : '../lang';
//#else */
const langBaseUrl = '../../lang';
let en_json;
if(IS_UNITTEST) {
// Use readFileSync, supported by node
const fs = await import('fs');
const en_file = fs.readFileSync('lang/en.json'); // readFileSync does not use a relative path
en_json = JSON.parse(en_file);
}
else {
// Use fetch - supported by browsers
const request = await fetch(new URL(`${langBaseUrl}/en.json`, import.meta.url));
en_json = await request.json();
}
//#endif
// Polyfill game.i18n until libWrapper initialises
export class i18n {
/*
* Initialisation
*/
static async _fetch(lang) {
/*#if _ROLLUP
// Avoid unnecessary requests if we know they're just going to 404
if(Array.isArray(i18nLangs) && !i18nLangs.includes(lang))
return null;
//#endif */
// Fetch language JSONs, if any
try {
const url = new URL(`${langBaseUrl}/${lang}.json`, import.meta.url);
const request = await fetch(url);
if(request.status !== 200 || !request.ok)
return null;
return request.json();
}
catch(e) {
Log.warn$?.(`Failed to load or parse ${url.href}.`, e);
return null;
}
}
static init() {
// Default members
this.jsons = [];
// Load languages
const langs = [];
try {
// client-scoped setting, but we do this before game.settings has initialised so have to grab it manually
const clientLanguage = game_settings_get('core', 'language', /*always_fallback=*/ true, /*return_null=*/ false);
if(clientLanguage && clientLanguage !== 'en')
langs.push(clientLanguage);
}
catch(e) {
Log.debug$?.(`Could not find or parse client language settings.`);
}
const serverLanguage = game?.i18n?.lang;
if(serverLanguage && serverLanguage !== 'en')
langs.push(serverLanguage);
// Fetch language JSONs asynchronously
let promises = [];
if(langs.length > 0) {
// Fetch languages
promises = langs.map((lang) => {
this._fetch(lang).then(
json => {
Log.debug$?.(`Loaded ${lang} language JSON.`);
if(json)
this.jsons.push(json);
}
);
});
}
return Promise.allSettled(promises);
}
static on_ready() {
// Allow garbage collection of JSONs
delete this.jsons;
//#if !_ROLLUP
en_json = undefined;
//#endif
}
/*
* Polyfill
*/
static localize(key) {
// Try real i18n library
if(game?.i18n) {
const res = game.i18n.localize(key);
if(res !== key)
return res;
}
// Fallback to polyfill
try {
const split = key.split('.');
// Try main language first
if(this.jsons) {
for(const json of this.jsons) {
const res = split.reduce((x,y) => x?.[y], json);
if(res)
return res;
}
}
// Default to just returning the key
return split.reduce((x,y) => x?.[y], en_json) ?? key;
}
catch(e) {
Log.error(e);
return key;
}
}
static format(key, args) {
// Try real i18n library
if(game?.i18n) {
const res = game.i18n.format(key, args);
if(res !== key)
return res;
}
// Fallback to polyfill
const localize = this.localize(key);
if(localize === key)
return localize;
try {
return localize.replace(/\{(.*?)\}/g, (_,y) => args?.[y]);
}
catch(e) {
Log.error(e);
return key;
}
}
}
// Set up a hook to cleanup once we are no longer a polyfill
if(!IS_UNITTEST)
Hooks.once('ready', i18n.on_ready.bind(i18n));