Skip to content

Commit

Permalink
lib: add navigator.language & navigator.languages
Browse files Browse the repository at this point in the history
PR-URL: nodejs#50303
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
  • Loading branch information
Uzlopak authored and anonrig committed Nov 9, 2023
1 parent 74c065b commit e0b1c61
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 3 deletions.
42 changes: 42 additions & 0 deletions doc/api/globals.md
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,47 @@ logical processors available to the current Node.js instance.
console.log(`This process is running on ${navigator.hardwareConcurrency} logical processors`);
```

### `navigator.language`

<!-- YAML
added: REPLACEME
-->

* {string}

The `navigator.language` read-only property returns a string representing the
preferred language of the Node.js instance. The language will be determined by
the ICU library used by Node.js at runtime based on the
default language of the operating system.

The value is representing the language version as defined in [RFC 5646][].

The fallback value on builds without ICU is `'en-US'`.

```js
console.log(`The preferred language of the Node.js instance has the tag '${navigator.language}'`);
```

### `navigator.languages`

<!-- YAML
added: REPLACEME
-->

* {Array<string>}

The `navigator.languages` read-only property returns an array of strings
representing the preferred languages of the Node.js instance.
By default `navigator.languages` contains only the value of
`navigator.language`, which will be determined by the ICU library used by
Node.js at runtime based on the default language of the operating system.

The fallback value on builds without ICU is `['en-US']`.

```js
console.log(`The preferred languages are '${navigator.languages}'`);
```

### `navigator.platform`

<!-- YAML
Expand Down Expand Up @@ -1100,6 +1141,7 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][].
[CommonJS module]: modules.md
[ECMAScript module]: esm.md
[Navigator API]: https://html.spec.whatwg.org/multipage/system-state.html#the-navigator-object
[RFC 5646]: https://www.rfc-editor.org/rfc/rfc5646.txt
[Web Crypto API]: webcrypto.md
[`--experimental-websocket`]: cli.md#--experimental-websocket
[`--no-experimental-global-customevent`]: cli.md#--no-experimental-global-customevent
Expand Down
24 changes: 24 additions & 0 deletions lib/internal/navigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ const {
StringPrototypeIndexOf,
StringPrototypeSlice,
StringPrototypeToUpperCase,
ObjectFreeze,
globalThis,
Symbol,
} = primordials;

const {
Intl,
} = globalThis;

const {
ERR_ILLEGAL_CONSTRUCTOR,
} = require('internal/errors').codes;
Expand Down Expand Up @@ -74,6 +80,8 @@ class Navigator {
#availableParallelism;
#userAgent = `Node.js/${StringPrototypeSlice(nodeVersion, 1, StringPrototypeIndexOf(nodeVersion, '.'))}`;
#platform = getNavigatorPlatform(process);
#language = Intl?.Collator().resolvedOptions().locale || 'en-US';
#languages = ObjectFreeze([this.#language]);

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

/**
* @return {string}
*/
get language() {
return this.#language;
}

/**
* @return {Array<string>}
*/
get languages() {
return this.#languages;
}

/**
* @return {string}
*/
Expand All @@ -107,6 +129,8 @@ class Navigator {

ObjectDefineProperties(Navigator.prototype, {
hardwareConcurrency: kEnumerableProperty,
language: kEnumerableProperty,
languages: kEnumerableProperty,
userAgent: kEnumerableProperty,
platform: kEnumerableProperty,
});
Expand Down
52 changes: 49 additions & 3 deletions test/parallel/test-navigator.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use strict';

// Flags: --expose-internals

require('../common');
'use strict';

const common = require('../common');
const assert = require('assert');
const { getNavigatorPlatform } = require('internal/navigator');
const { execFile } = require('child_process');

const is = {
number: (value, key) => {
Expand Down Expand Up @@ -74,3 +75,48 @@ assert.strictEqual(getNavigatorPlatform({ arch: 'ia32', platform: 'sunos' }), 'S
assert.strictEqual(getNavigatorPlatform({ arch: 'x64', platform: 'sunos' }), 'SunOS x64');
assert.strictEqual(getNavigatorPlatform({ arch: 'ppc', platform: 'aix' }), 'AIX');
assert.strictEqual(getNavigatorPlatform({ arch: 'x64', platform: 'reactos' }), 'Reactos x64');

assert.strictEqual(typeof navigator.language, 'string');
assert.strictEqual(navigator.language.length !== 0, true);

assert.ok(Array.isArray(navigator.languages));
assert.strictEqual(navigator.languages.length, 1);
assert.strictEqual(typeof navigator.languages[0], 'string');
assert.strictEqual(navigator.languages[0].length !== 0, true);

assert.throws(() => {
navigator.languages[0] = 'foo';
}, new TypeError("Cannot assign to read only property '0' of object '[object Array]'"));
assert.notStrictEqual(navigator.languages[0], 'foo');
assert.strictEqual(typeof navigator.languages[0] === 'string', true);
assert.strictEqual(navigator.languages[0].length !== 0, true);

if (common.hasIntl && common.isWindows === false) {
const testLocale = navigator.language === 'de-DE' ?
'en-US' :
'de-DE';
{
const env = { ...process.env, LC_ALL: testLocale };
execFile(
process.execPath,
['--print', `"process.exit(navigator.language === '${testLocale}' ? 0 : 1)"`],
{ env },
common.mustSucceed()
);
}

{
const env = { ...process.env, LC_ALL: testLocale };
execFile(
process.execPath,
['--print', `"process.exit(navigator.languages[0] === '${testLocale}' ? 0 : 1)"`],
{ env },
common.mustSucceed()
);
}
}

Object.defineProperty(navigator, 'language', { value: 'for-testing' });
assert.strictEqual(navigator.language, 'for-testing');
assert.strictEqual(navigator.languages.length, 1);
assert.strictEqual(navigator.languages[0] !== 'for-testing', true);

0 comments on commit e0b1c61

Please sign in to comment.