Skip to content

Commit

Permalink
lib: add navigator.language and navigator.languages
Browse files Browse the repository at this point in the history
  • Loading branch information
Uzlopak committed Oct 20, 2023
1 parent b30acb7 commit f82d7a3
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 0 deletions.
34 changes: 34 additions & 0 deletions doc/api/globals.md
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,40 @@ logical processors available to the current Node.js instance.
console.log(`This process is running on ${navigator.hardwareConcurrency}`);
```

### `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 value is representing the language version as defined in RFC <5646>.
Examples of valid language codes include "en", "en-US", "fr", "fr-FR", "es-ES",
etc.

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

### `navigator.languages`

<!-- YAML
added: REPLACEME
-->

* {string}

The `navigator.language` read-only property returns a string representing the
preferred language of the Node.js instance.

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

## `PerformanceEntry`

<!-- YAML
Expand Down
18 changes: 18 additions & 0 deletions lib/internal/navigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const {
ObjectDefineProperties,
ObjectFreeze,
Symbol,
} = primordials;

Expand All @@ -17,11 +18,18 @@ const {
getAvailableParallelism,
} = internalBinding('os');

const {
getDefaultLocale,
getAvailableLocales,
} = internalBinding('icu');

const kInitialize = Symbol('kInitialize');

class Navigator {
// Private properties are used to avoid brand validations.
#availableParallelism;
#language;
#languages;

constructor() {
if (arguments[0] === kInitialize) {
Expand All @@ -37,6 +45,16 @@ class Navigator {
this.#availableParallelism ??= getAvailableParallelism();
return this.#availableParallelism;
}

get language() {
this.#language ??= getDefaultLocale();
return this.#language;
}

get languages() {
this.#languages ??= ObjectFreeze(getAvailableLocales());
return this.#languages;
}
}

ObjectDefineProperties(Navigator.prototype, {
Expand Down
77 changes: 77 additions & 0 deletions src/node_i18n.cc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
#include <unicode/ucnv.h>
#include <unicode/udata.h>
#include <unicode/uidna.h>
#include <unicode/uloc.h>
#include <unicode/ulocdata.h>
#include <unicode/urename.h>
#include <unicode/ustring.h>
Expand Down Expand Up @@ -601,6 +602,78 @@ void SetDefaultTimeZone(const char* tzid) {
CHECK(U_SUCCESS(status));
}

static void GetDefaultLocale(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
const char* locale = uloc_getDefault();

std::string localeStr(locale);

for (char& c : localeStr) {
if (c == '_') {
c = '-';
}
}

args.GetReturnValue().Set(
String::NewFromUtf8(env->isolate(),
localeStr.c_str(),
NewStringType::kNormal,
static_cast<int>(localeStr.size()))
.ToLocalChecked());
}

void GetAvailableLocales(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
int32_t locCount = uloc_countAvailable() - 1;

v8::Local<v8::Array> locales = v8::Array::New(isolate, locCount);
v8::Local<v8::Context> context = isolate->GetCurrentContext();

const char* defaultLocale = uloc_getDefault();
std::string defaultLocaleStr(defaultLocale);

for (char& c : defaultLocaleStr) {
if (c == '_') {
c = '-';
}
}

v8::Local<v8::String> defaultModifiedLocaleStr =
v8::String::NewFromUtf8(isolate,
defaultLocaleStr.c_str(),
v8::NewStringType::kNormal,
static_cast<int>(defaultLocaleStr.size()))
.ToLocalChecked();

locales->Set(context, 0, defaultModifiedLocaleStr).FromJust();

for (int32_t i = 0; i < locCount; ++i) {
const char* locale = uloc_getAvailable(i);

std::string localeStr(locale);

for (char& c : localeStr) {
if (c == '_') {
c = '-';
}
}

if (localeStr.compare(defaultLocaleStr) == 0) {
continue;
}

v8::Local<v8::String> modifiedValue =
v8::String::NewFromUtf8(isolate,
localeStr.c_str(),
v8::NewStringType::kNormal,
static_cast<int>(localeStr.size()))
.ToLocalChecked();

locales->Set(context, i + 1, modifiedValue).FromJust();
}
args.GetReturnValue().Set(locales);
}

int32_t ToUnicode(MaybeStackBuffer<char>* buf,
const char* input,
size_t length) {
Expand Down Expand Up @@ -890,6 +963,8 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
SetMethod(isolate, target, "getConverter", ConverterObject::Create);
SetMethod(isolate, target, "decode", ConverterObject::Decode);
SetMethod(isolate, target, "hasConverter", ConverterObject::Has);
SetMethod(isolate, target, "getDefaultLocale", GetDefaultLocale);
SetMethod(isolate, target, "getAvailableLocales", GetAvailableLocales);
}

void CreatePerContextProperties(Local<Object> target,
Expand All @@ -903,6 +978,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(GetStringWidth);
registry->Register(ICUErrorName);
registry->Register(Transcode);
registry->Register(GetDefaultLocale);
registry->Register(GetAvailableLocales);
registry->Register(ConverterObject::Create);
registry->Register(ConverterObject::Decode);
registry->Register(ConverterObject::Has);
Expand Down
9 changes: 9 additions & 0 deletions test/parallel/test-navigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,12 @@ const is = {
is.number(+navigator.hardwareConcurrency, 'hardwareConcurrency');
is.number(navigator.hardwareConcurrency, 'hardwareConcurrency');
assert.ok(navigator.hardwareConcurrency > 0);

assert.strictEqual(typeof navigator.language, 'string');
assert.ok(navigator.language.length > 0);

assert.ok(Array.isArray(navigator.languages));
assert.strictEqual(typeof navigator.languages[0], 'string');

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

Check failure on line 23 in test/parallel/test-navigator.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

This line has a length of 139. Maximum allowed is 120

Check failure on line 23 in test/parallel/test-navigator.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Requires a space after '{'

Check failure on line 23 in test/parallel/test-navigator.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Requires a space before '}'

Check failure on line 23 in test/parallel/test-navigator.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing semicolon
assert.notStrictEqual(navigator.languages[0], 'foo');

0 comments on commit f82d7a3

Please sign in to comment.