Skip to content

Commit f82d7a3

Browse files
committed
lib: add navigator.language and navigator.languages
1 parent b30acb7 commit f82d7a3

File tree

4 files changed

+138
-0
lines changed

4 files changed

+138
-0
lines changed

doc/api/globals.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,40 @@ logical processors available to the current Node.js instance.
629629
console.log(`This process is running on ${navigator.hardwareConcurrency}`);
630630
```
631631

632+
### `navigator.language`
633+
634+
<!-- YAML
635+
added: REPLACEME
636+
-->
637+
638+
* {string}
639+
640+
The `navigator.language` read-only property returns a string representing the
641+
preferred language of the Node.js instance.
642+
643+
The value is representing the language version as defined in RFC <5646>.
644+
Examples of valid language codes include "en", "en-US", "fr", "fr-FR", "es-ES",
645+
etc.
646+
647+
```js
648+
console.log(`The preferred language of the Node.js instance has the tag '${navigator.language}'`);
649+
```
650+
651+
### `navigator.languages`
652+
653+
<!-- YAML
654+
added: REPLACEME
655+
-->
656+
657+
* {string}
658+
659+
The `navigator.language` read-only property returns a string representing the
660+
preferred language of the Node.js instance.
661+
662+
```js
663+
console.log(`The preferred language has the tag '${navigator.language}'`);
664+
```
665+
632666
## `PerformanceEntry`
633667

634668
<!-- YAML

lib/internal/navigator.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const {
44
ObjectDefineProperties,
5+
ObjectFreeze,
56
Symbol,
67
} = primordials;
78

@@ -17,11 +18,18 @@ const {
1718
getAvailableParallelism,
1819
} = internalBinding('os');
1920

21+
const {
22+
getDefaultLocale,
23+
getAvailableLocales,
24+
} = internalBinding('icu');
25+
2026
const kInitialize = Symbol('kInitialize');
2127

2228
class Navigator {
2329
// Private properties are used to avoid brand validations.
2430
#availableParallelism;
31+
#language;
32+
#languages;
2533

2634
constructor() {
2735
if (arguments[0] === kInitialize) {
@@ -37,6 +45,16 @@ class Navigator {
3745
this.#availableParallelism ??= getAvailableParallelism();
3846
return this.#availableParallelism;
3947
}
48+
49+
get language() {
50+
this.#language ??= getDefaultLocale();
51+
return this.#language;
52+
}
53+
54+
get languages() {
55+
this.#languages ??= ObjectFreeze(getAvailableLocales());
56+
return this.#languages;
57+
}
4058
}
4159

4260
ObjectDefineProperties(Navigator.prototype, {

src/node_i18n.cc

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
#include <unicode/ucnv.h>
6262
#include <unicode/udata.h>
6363
#include <unicode/uidna.h>
64+
#include <unicode/uloc.h>
6465
#include <unicode/ulocdata.h>
6566
#include <unicode/urename.h>
6667
#include <unicode/ustring.h>
@@ -601,6 +602,78 @@ void SetDefaultTimeZone(const char* tzid) {
601602
CHECK(U_SUCCESS(status));
602603
}
603604

605+
static void GetDefaultLocale(const FunctionCallbackInfo<Value>& args) {
606+
Environment* env = Environment::GetCurrent(args);
607+
const char* locale = uloc_getDefault();
608+
609+
std::string localeStr(locale);
610+
611+
for (char& c : localeStr) {
612+
if (c == '_') {
613+
c = '-';
614+
}
615+
}
616+
617+
args.GetReturnValue().Set(
618+
String::NewFromUtf8(env->isolate(),
619+
localeStr.c_str(),
620+
NewStringType::kNormal,
621+
static_cast<int>(localeStr.size()))
622+
.ToLocalChecked());
623+
}
624+
625+
void GetAvailableLocales(const v8::FunctionCallbackInfo<v8::Value>& args) {
626+
v8::Isolate* isolate = args.GetIsolate();
627+
int32_t locCount = uloc_countAvailable() - 1;
628+
629+
v8::Local<v8::Array> locales = v8::Array::New(isolate, locCount);
630+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
631+
632+
const char* defaultLocale = uloc_getDefault();
633+
std::string defaultLocaleStr(defaultLocale);
634+
635+
for (char& c : defaultLocaleStr) {
636+
if (c == '_') {
637+
c = '-';
638+
}
639+
}
640+
641+
v8::Local<v8::String> defaultModifiedLocaleStr =
642+
v8::String::NewFromUtf8(isolate,
643+
defaultLocaleStr.c_str(),
644+
v8::NewStringType::kNormal,
645+
static_cast<int>(defaultLocaleStr.size()))
646+
.ToLocalChecked();
647+
648+
locales->Set(context, 0, defaultModifiedLocaleStr).FromJust();
649+
650+
for (int32_t i = 0; i < locCount; ++i) {
651+
const char* locale = uloc_getAvailable(i);
652+
653+
std::string localeStr(locale);
654+
655+
for (char& c : localeStr) {
656+
if (c == '_') {
657+
c = '-';
658+
}
659+
}
660+
661+
if (localeStr.compare(defaultLocaleStr) == 0) {
662+
continue;
663+
}
664+
665+
v8::Local<v8::String> modifiedValue =
666+
v8::String::NewFromUtf8(isolate,
667+
localeStr.c_str(),
668+
v8::NewStringType::kNormal,
669+
static_cast<int>(localeStr.size()))
670+
.ToLocalChecked();
671+
672+
locales->Set(context, i + 1, modifiedValue).FromJust();
673+
}
674+
args.GetReturnValue().Set(locales);
675+
}
676+
604677
int32_t ToUnicode(MaybeStackBuffer<char>* buf,
605678
const char* input,
606679
size_t length) {
@@ -890,6 +963,8 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
890963
SetMethod(isolate, target, "getConverter", ConverterObject::Create);
891964
SetMethod(isolate, target, "decode", ConverterObject::Decode);
892965
SetMethod(isolate, target, "hasConverter", ConverterObject::Has);
966+
SetMethod(isolate, target, "getDefaultLocale", GetDefaultLocale);
967+
SetMethod(isolate, target, "getAvailableLocales", GetAvailableLocales);
893968
}
894969

895970
void CreatePerContextProperties(Local<Object> target,
@@ -903,6 +978,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
903978
registry->Register(GetStringWidth);
904979
registry->Register(ICUErrorName);
905980
registry->Register(Transcode);
981+
registry->Register(GetDefaultLocale);
982+
registry->Register(GetAvailableLocales);
906983
registry->Register(ConverterObject::Create);
907984
registry->Register(ConverterObject::Decode);
908985
registry->Register(ConverterObject::Has);

test/parallel/test-navigator.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,12 @@ const is = {
1313
is.number(+navigator.hardwareConcurrency, 'hardwareConcurrency');
1414
is.number(navigator.hardwareConcurrency, 'hardwareConcurrency');
1515
assert.ok(navigator.hardwareConcurrency > 0);
16+
17+
assert.strictEqual(typeof navigator.language, 'string');
18+
assert.ok(navigator.language.length > 0);
19+
20+
assert.ok(Array.isArray(navigator.languages));
21+
assert.strictEqual(typeof navigator.languages[0], 'string');
22+
23+
assert.throws(() => {navigator.languages[0] = 'foo'}, new TypeError("Cannot assign to read only property '0' of object '[object Array]'"));
24+
assert.notStrictEqual(navigator.languages[0], 'foo');

0 commit comments

Comments
 (0)