-
Notifications
You must be signed in to change notification settings - Fork 39
Expand file tree
/
Copy pathlanguage.js
More file actions
322 lines (283 loc) · 12.3 KB
/
language.js
File metadata and controls
322 lines (283 loc) · 12.3 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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
/*
* Windows Language
*
* Copyright 2018 Raising the Floor - International
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* The R&D leading to these results received funding from the
* Department of Education - Grant H421A150005 (GPII-APCP). However,
* these results do not necessarily represent the policy of the
* Department of Education, and you should not assume endorsement by the
* Federal Government.
*
* You may obtain a copy of the License at
* https://github.com/GPII/universal/blob/master/LICENSE.txt
*/
"use strict";
var fluid = require("gpii-universal"),
child_process = require("child_process");
var gpii = fluid.registerNamespace("gpii");
fluid.registerNamespace("gpii.windows.language");
fluid.defaults("gpii.windows.language", {
gradeNames: ["fluid.component", "fluid.modelComponent"],
invokers: {
getInstalledLanguages: {
funcName: "gpii.windows.language.getInstalled",
args: [ "{that}" ]
},
getLanguageNames: {
funcName: "gpii.windows.language.getLanguageNames",
args: [ "{that}", "{arguments}.0" ]
},
getDisplayLanguage: {
funcName: "gpii.windows.language.getDisplayLanguage"
},
startMessages: "{gpii.windows.messages}.start({that})",
stopMessages: "{gpii.windows.messages}.stop({that})"
},
listeners: {
"onCreate.update": "{that}.getInstalledLanguages()",
"onCreate.messages": "{that}.startMessages()",
"{gpii.windows.messages}.events.onMessage": {
funcName: "gpii.windows.language.windowMessage",
// that, hwnd, msg, wParam, lParam
args: [ "{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2", "{arguments}.3" ]
}
},
// The model gets updated whenever getInstalledLanguages is called.
model: {
/** @type {Map<String,InstalledLanguage>} */
installedLanguages: null,
/** Currently configured display language */
configuredLanguage: null
},
members: {
/** code=>name map of language names in english */
englishNames: {}
}
});
/**
* Language names.
* @typedef {Object} LanguageNames
* @property {String} english The language name in English.
* @property {String} local The language name (and country), in the current display language.
* @property {String} native The language name (and country), in its native language.
*/
/**
* An installed language
* @typedef {LanguageNames} InstalledLanguage
* @property {String} code The language code, `lang[-COUNTRY]`.
* @property {Boolean} current true if this is the current display language.
*/
/**
* Fixes the casing of a language code, so the language is lowercase and the region is uppercase, and add the region
* part if it's not provided.
*
* @param {String} langCode The language code.
* @return {String} The language code, in the correct casing.
*/
gpii.windows.language.fixLangCode = function (langCode) {
var LOCALE_NAME_MAX_LENGTH = 85;
var langCodeBuffer = gpii.windows.stringToWideChar(langCode);
var fixedBuffer = Buffer.alloc(LOCALE_NAME_MAX_LENGTH * 2);
var result = gpii.windows.kernel32.ResolveLocaleName(langCodeBuffer, fixedBuffer, LOCALE_NAME_MAX_LENGTH);
var togo;
if (result) {
togo = gpii.windows.stringFromWideChar(fixedBuffer);
} else {
fluid.log(gpii.windows.win32errorText("ResolveLocaleName", result));
togo = langCode;
}
return togo;
};
/**
* Gets the display languages that are installed on the system, and updates the model.
*
* These are listed retrieved from the MUILanguages field of the Win32_OperatingSystem WMI class.
*
* @param {Component} that The gpii.windows.language instance.
* @return {Promise<Map<String,InstalledLanguage>>} A promise, resolving with either the language names if the list has
* changed, or null if there was no change.
*/
gpii.windows.language.getInstalled = function (that) {
// Get the installed language packs
var languagePacks = gpii.windows.wmi.getQuery([null, "SELECT MUILanguages FROM Win32_OperatingSystem"]);
var langCodes = languagePacks.map(gpii.windows.language.fixLangCode);
var current = gpii.windows.language.getDisplayLanguage();
// Because this function gets called in the off-chance that the language has changed (maybe multiple times during
// key-in), and getting the translated languages requires a new process, perform some checks upfront.
var changed = current !== fluid.get(that.model, "configuredLanguage");
that.applier.change("configuredLanguage", current);
if (!changed) {
var knownLanguages = Object.keys(fluid.get(that.model, "installedLanguages") || {});
changed = langCodes.length !== knownLanguages.length;
if (!changed) {
changed = !knownLanguages.every(function (elem) {
return langCodes.includes(elem);
});
}
}
var promise;
if (changed) {
// Update the language names only if required.
promise = that.getLanguageNames(langCodes).then(function (languages) {
that.applier.change("installedLanguages", languages);
});
} else {
promise = fluid.promise().resolve();
}
return promise;
};
/**
* Gets the language names of the given languages, identified by their IETF language codes (`en`, `es-MX`).
*
* It returns an object containing the name in English, the current display language, and native language.
*
* If only the language identifier (first 2 characters) are passed, then the language name is returned.
* If the country code is also given, then the country is also returned in brackets:
* - If the country is code is unknown, or the country-specific language isn't recognised, then the language code is
* used instead of the country.
* - If the language is only spoken in a single country (eg, Bulgarian), then the country is not returned, unless a
* different country was passed (eg, bg-GB).
* If the language is unknown, then an empty string is used. If the language code is invalid, null each field is null.
*
* Examples:
*```
* "es-MX" => { english: "Spanish (Mexico)", local: "Spanish (Mexico)", native: "Español (México)" }
* "en" => { english: "English", local: "English", native: "English" }
* "en-GB" => { "english": "English (United Kingdom)", "local": "English (United Kingdom)", "native": "English (United Kingdom)" }
*```
* When the current display language is French:
* ```
* "nl-NL" => { english: "Dutch (Netherlands)", local: "Néerlandais (Pays-Bas)", native: "Nederlands (Nederland)" }
* ```
* @param {Component} that The gpii.windows.language instance.
* @param {String|Array<String>} langCodes The language code(s), in the form of `lang[-COUNTRY]`.
* @return {Promise<LanguageNames>} A promise, resolving with the language names.
*/
gpii.windows.language.getLanguageNames = function (that, langCodes) {
var options = {
env: {
GPII_LANG_CODES: JSON.stringify(fluid.makeArray(langCodes))
},
execArgv: []
};
// In order for the localised names to be in the current display language, the language name routine needs to be
// called in a new process. An attempt had been made to load and unload the winlangdb library in this process,
// however the locale remained the same as it was the first time it was loaded.
var child = child_process.fork(__dirname + "/languageNames.js", options);
var promise = fluid.promise();
var timer = setTimeout(function () {
child.kill();
timer = null;
}, 10000);
child.on("message", function (result) {
var current = gpii.windows.language.getDisplayLanguage();
var currentlyEnglish = current.startsWith("en");
var languages = fluid.transform(result, function (names, code) {
// The english field only contains the language, without the country - but the full name is desired.
// Get this from the .local field if the current language is English. It is hoped that the first time this
// is called, the current language is English. Otherwise, the "english" value will only contain the
// country until this is called when the current language is English.
var englishName = that.englishNames[code];
if (currentlyEnglish && !englishName) {
englishName = names.local;
// Save it for when the current language isn't English
that.englishNames[code] = englishName;
}
if (englishName) {
names.english = englishName;
}
names.code = code;
if (code === current) {
names.current = true;
}
return names;
});
promise.resolve(languages);
});
child.on("exit", function (code) {
if (timer) {
clearTimeout(timer);
}
if (!promise.disposition) {
fluid.log("languageNames failed");
promise.reject({
isError: true,
message: timer
? "language name translation failed (" + code + ")"
: "Timed out waiting for the language names"
});
}
});
return promise;
};
/**
* Called when an event has been received by the message window.
*
* When a relevant message is received, the installed languages model will be updated. The current language can't be
* changed during a session, however the drop-down list in control panel still broadcasts WM_SETTINGCHANGE.
*
* @param {Component} that The gpii.windows.language component.
* @param {Number} hwnd The window handle of the message window.
* @param {Number|String} msg The message identifier.
* @param {Number} wParam Message specific data.
* #param {Buffer} lParam Additional message specific data. (unused)
*/
gpii.windows.language.windowMessage = function (that, hwnd, msg, wParam) {
if (msg === "GPII-TrayButton-Message" && wParam === 1
|| msg === gpii.windows.API_constants.WM_INPUTLANGCHANGE) {
that.getInstalledLanguages();
}
};
/**
* Gets the currently configured display language.
*
* This is the language which new processes will use.
*
* @return {String} The language code of the currently configured display language.
*/
gpii.windows.language.getDisplayLanguage = function () {
var langCode = gpii.windows.readRegistryKey(
"HKEY_CURRENT_USER", "Control Panel\\Desktop", "PreferredUILanguages", "REG_SZ").value;
// This setting could be an empty string (perhaps if the language has never been changed?)
if (!langCode) {
// Get the thread's locale, then get the corresponding language code.
var LOCALE_NAME_MAX_LENGTH = 85;
var lcid = gpii.windows.kernel32.GetThreadUILanguage();
var codeBuffer = Buffer.alloc(LOCALE_NAME_MAX_LENGTH);
var result = gpii.windows.kernel32.LCIDToLocaleName(lcid, codeBuffer, codeBuffer.length, 0);
if (result > 0) {
langCode = gpii.windows.stringFromWideChar(codeBuffer);
} else {
fluid.log(gpii.windows.win32errorText("LCIDToLocaleName failed", result));
}
}
return gpii.windows.language.fixLangCode(langCode);
};
/**
* Updates the Windows display language, by restarting explorer if the language has changed since the last time
* this was called.
*
* @param {String} currentLanguage [optional] The current (new) language.
* @return {Promise|undefined} Resolves when explorer has restarted, or null if the language has not changed.
*/
gpii.windows.updateLanguage = function (currentLanguage) {
var lang = currentLanguage || gpii.windows.language.getDisplayLanguage();
if (gpii.windows.updateLanguage.lastLanguage !== lang) {
// Update the state.
var languageInstances = fluid.queryIoCSelector(fluid.rootComponent, "gpii.windows.language");
fluid.each(languageInstances, gpii.windows.language.getInstalled);
gpii.windows.updateLanguage.lastLanguage = lang;
return gpii.windows.restartExplorer();
}
};
gpii.windows.updateLanguage.lastLanguage = gpii.windows.language.getDisplayLanguage();
fluid.defaults("gpii.windows.updateLanguage", {
gradeNames: "fluid.function",
argumentMap: {
currentLanguage: 0
}
});