Skip to content

Commit 80f5bee

Browse files
Uzlopaktargos
authored andcommitted
lib: add navigator.language & navigator.languages
PR-URL: #50303 Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Geoffrey Booth <[email protected]>
1 parent 3ff4afc commit 80f5bee

File tree

3 files changed

+115
-3
lines changed

3 files changed

+115
-3
lines changed

doc/api/globals.md

+42
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,47 @@ logical processors available to the current Node.js instance.
637637
console.log(`This process is running on ${navigator.hardwareConcurrency} logical processors`);
638638
```
639639

640+
### `navigator.language`
641+
642+
<!-- YAML
643+
added: REPLACEME
644+
-->
645+
646+
* {string}
647+
648+
The `navigator.language` read-only property returns a string representing the
649+
preferred language of the Node.js instance. The language will be determined by
650+
the ICU library used by Node.js at runtime based on the
651+
default language of the operating system.
652+
653+
The value is representing the language version as defined in [RFC 5646][].
654+
655+
The fallback value on builds without ICU is `'en-US'`.
656+
657+
```js
658+
console.log(`The preferred language of the Node.js instance has the tag '${navigator.language}'`);
659+
```
660+
661+
### `navigator.languages`
662+
663+
<!-- YAML
664+
added: REPLACEME
665+
-->
666+
667+
* {Array<string>}
668+
669+
The `navigator.languages` read-only property returns an array of strings
670+
representing the preferred languages of the Node.js instance.
671+
By default `navigator.languages` contains only the value of
672+
`navigator.language`, which will be determined by the ICU library used by
673+
Node.js at runtime based on the default language of the operating system.
674+
675+
The fallback value on builds without ICU is `['en-US']`.
676+
677+
```js
678+
console.log(`The preferred languages are '${navigator.languages}'`);
679+
```
680+
640681
### `navigator.platform`
641682

642683
<!-- YAML
@@ -1100,6 +1141,7 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][].
11001141
[CommonJS module]: modules.md
11011142
[ECMAScript module]: esm.md
11021143
[Navigator API]: https://html.spec.whatwg.org/multipage/system-state.html#the-navigator-object
1144+
[RFC 5646]: https://www.rfc-editor.org/rfc/rfc5646.txt
11031145
[Web Crypto API]: webcrypto.md
11041146
[`--experimental-websocket`]: cli.md#--experimental-websocket
11051147
[`--no-experimental-global-customevent`]: cli.md#--no-experimental-global-customevent

lib/internal/navigator.js

+24
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@ const {
55
StringPrototypeIndexOf,
66
StringPrototypeSlice,
77
StringPrototypeToUpperCase,
8+
ObjectFreeze,
9+
globalThis,
810
Symbol,
911
} = primordials;
1012

13+
const {
14+
Intl,
15+
} = globalThis;
16+
1117
const {
1218
ERR_ILLEGAL_CONSTRUCTOR,
1319
} = require('internal/errors').codes;
@@ -74,6 +80,8 @@ class Navigator {
7480
#availableParallelism;
7581
#userAgent = `Node.js/${StringPrototypeSlice(nodeVersion, 1, StringPrototypeIndexOf(nodeVersion, '.'))}`;
7682
#platform = getNavigatorPlatform(process);
83+
#language = Intl?.Collator().resolvedOptions().locale || 'en-US';
84+
#languages = ObjectFreeze([this.#language]);
7785

7886
constructor() {
7987
if (arguments[0] === kInitialize) {
@@ -90,6 +98,20 @@ class Navigator {
9098
return this.#availableParallelism;
9199
}
92100

101+
/**
102+
* @return {string}
103+
*/
104+
get language() {
105+
return this.#language;
106+
}
107+
108+
/**
109+
* @return {Array<string>}
110+
*/
111+
get languages() {
112+
return this.#languages;
113+
}
114+
93115
/**
94116
* @return {string}
95117
*/
@@ -107,6 +129,8 @@ class Navigator {
107129

108130
ObjectDefineProperties(Navigator.prototype, {
109131
hardwareConcurrency: kEnumerableProperty,
132+
language: kEnumerableProperty,
133+
languages: kEnumerableProperty,
110134
userAgent: kEnumerableProperty,
111135
platform: kEnumerableProperty,
112136
});

test/parallel/test-navigator.js

+49-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
'use strict';
2-
31
// Flags: --expose-internals
42

5-
require('../common');
3+
'use strict';
4+
5+
const common = require('../common');
66
const assert = require('assert');
77
const { getNavigatorPlatform } = require('internal/navigator');
8+
const { execFile } = require('child_process');
89

910
const is = {
1011
number: (value, key) => {
@@ -74,3 +75,48 @@ assert.strictEqual(getNavigatorPlatform({ arch: 'ia32', platform: 'sunos' }), 'S
7475
assert.strictEqual(getNavigatorPlatform({ arch: 'x64', platform: 'sunos' }), 'SunOS x64');
7576
assert.strictEqual(getNavigatorPlatform({ arch: 'ppc', platform: 'aix' }), 'AIX');
7677
assert.strictEqual(getNavigatorPlatform({ arch: 'x64', platform: 'reactos' }), 'Reactos x64');
78+
79+
assert.strictEqual(typeof navigator.language, 'string');
80+
assert.strictEqual(navigator.language.length !== 0, true);
81+
82+
assert.ok(Array.isArray(navigator.languages));
83+
assert.strictEqual(navigator.languages.length, 1);
84+
assert.strictEqual(typeof navigator.languages[0], 'string');
85+
assert.strictEqual(navigator.languages[0].length !== 0, true);
86+
87+
assert.throws(() => {
88+
navigator.languages[0] = 'foo';
89+
}, new TypeError("Cannot assign to read only property '0' of object '[object Array]'"));
90+
assert.notStrictEqual(navigator.languages[0], 'foo');
91+
assert.strictEqual(typeof navigator.languages[0] === 'string', true);
92+
assert.strictEqual(navigator.languages[0].length !== 0, true);
93+
94+
if (common.hasIntl && common.isWindows === false) {
95+
const testLocale = navigator.language === 'de-DE' ?
96+
'en-US' :
97+
'de-DE';
98+
{
99+
const env = { ...process.env, LC_ALL: testLocale };
100+
execFile(
101+
process.execPath,
102+
['--print', `"process.exit(navigator.language === '${testLocale}' ? 0 : 1)"`],
103+
{ env },
104+
common.mustSucceed()
105+
);
106+
}
107+
108+
{
109+
const env = { ...process.env, LC_ALL: testLocale };
110+
execFile(
111+
process.execPath,
112+
['--print', `"process.exit(navigator.languages[0] === '${testLocale}' ? 0 : 1)"`],
113+
{ env },
114+
common.mustSucceed()
115+
);
116+
}
117+
}
118+
119+
Object.defineProperty(navigator, 'language', { value: 'for-testing' });
120+
assert.strictEqual(navigator.language, 'for-testing');
121+
assert.strictEqual(navigator.languages.length, 1);
122+
assert.strictEqual(navigator.languages[0] !== 'for-testing', true);

0 commit comments

Comments
 (0)