Skip to content

Commit

Permalink
lib: set locale per cli
Browse files Browse the repository at this point in the history
  • Loading branch information
Uzlopak committed Nov 4, 2023
1 parent 8373643 commit 49423d0
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 0 deletions.
15 changes: 15 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,19 @@ surface on other platforms, but the performance impact may be severe.
This flag is inherited from V8 and is subject to change upstream. It may
disappear in a non-semver-major release.

### `--locale`

<!-- YAML
added: REPLACEME
-->

Set the default locale used by ICU (`Intl` object). It expects a string
representing the language version as defined in [RFC 5646][] (also known as
BCP 47).

Examples of valid language codes include "en", "en-US", "fr", "fr-FR", "es-ES",
etc.

### `--max-http-header-size=size`

<!-- YAML
Expand Down Expand Up @@ -2344,6 +2357,7 @@ Node.js options that are allowed are:
* `--inspect-port`, `--debug-port`
* `--inspect-publish-uid`
* `--inspect`
* `--locale`
* `--max-http-header-size`
* `--napi-modules`
* `--no-addons`
Expand Down Expand Up @@ -2769,6 +2783,7 @@ done
[OSSL_PROVIDER-legacy]: https://www.openssl.org/docs/man3.0/man7/OSSL_PROVIDER-legacy.html
[Permission Model]: permissions.md#permission-model
[REPL]: repl.md
[RFC 5646]: https://www.rfc-editor.org/rfc/rfc5646.txt
[ScriptCoverage]: https://chromedevtools.github.io/devtools-protocol/tot/Profiler#type-ScriptCoverage
[ShadowRealm]: https://github.com/tc39/proposal-shadowrealm
[Source Map]: https://sourcemaps.info/spec.html
Expand Down
4 changes: 4 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,10 @@ static ExitCode InitializeNodeWithArgsInternal(
}
# endif

if (!per_process::cli_options->locale.empty()) {
i18n::SetDefaultLocale(per_process::cli_options->locale.c_str());
}

#endif // defined(NODE_HAVE_I18N_SUPPORT)

// We should set node_is_initialized here instead of in node::Start,
Expand Down
8 changes: 8 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,13 @@ void SetDefaultTimeZone(const char* tzid) {
CHECK(U_SUCCESS(status));
}

void SetDefaultLocale(const char* localeid) {
UErrorCode status = U_ZERO_ERROR;

uloc_setDefault(localeid, &status);
CHECK(U_SUCCESS(status));
}

int32_t ToUnicode(MaybeStackBuffer<char>* buf,
const char* input,
size_t length) {
Expand Down
2 changes: 2 additions & 0 deletions src/node_i18n.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ bool InitializeICUDirectory(const std::string& path, std::string* error);

void SetDefaultTimeZone(const char* tzid);

void SetDefaultLocale(const char* localid);

enum class idna_mode {
// Default mode for maximum compatibility.
kDefault,
Expand Down
5 changes: 5 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,11 @@ PerProcessOptionsParser::PerProcessOptionsParser(
,
&PerProcessOptions::icu_data_dir,
kAllowedInEnvvar);

AddOption("--locale",
"Set the locale of the node instance",
&PerProcessOptions::locale,
kAllowedInEnvvar);
#endif

#if HAVE_OPENSSL
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ class PerProcessOptions : public Options {

#ifdef NODE_HAVE_I18N_SUPPORT
std::string icu_data_dir;
std::string locale;
#endif

// Per-process because they affect singleton OpenSSL shared library state,
Expand Down
176 changes: 176 additions & 0 deletions test/parallel/test-icu-locale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
'use strict';
const common = require('../common');
const { spawnSyncAndExitWithoutError } = require('../common/child_process');

if (!common.hasIntl)
common.skip('Intl not present.');

spawnSyncAndExitWithoutError(
process.execPath,
[
'--locale=fr-FR',
'-p',
'Intl?.Collator().resolvedOptions().locale',
],
{
signal: null,
stdout: /fr-FR/,
});

spawnSyncAndExitWithoutError(
process.execPath,
[
'--locale=de-DE',
'-p',
'Intl?.Collator().resolvedOptions().locale',
],
{
signal: null,
stdout: /de-DE/,
});

// NumberFormat
spawnSyncAndExitWithoutError(
process.execPath,
['--locale=de-DE', '-p', '(123456.789).toLocaleString(undefined, { style: "currency", currency: "EUR" })'],
{
signal: null,
stdout: /123\.456,79\xa0€/u,
});

spawnSyncAndExitWithoutError(
process.execPath,
[
'--locale=jp-JP',
'-p',
'(123456.789).toLocaleString(undefined, { style: "currency", currency: "JPY" })',
],
{
signal: null,
stdout: /JP¥\xa0123,457/u,
});

spawnSyncAndExitWithoutError(
process.execPath,
[
'--locale=en-IN',
'-p',
'(123456.789).toLocaleString(undefined, { maximumSignificantDigits: 3 })',
],
{
signal: null,
stdout: /1,23,000/u,
});

// DateTimeFormat
spawnSyncAndExitWithoutError(
process.execPath,
[
'--locale=en-GB',
'-p',
'new Date(Date.UTC(2012, 11, 20, 3, 0, 0)).toLocaleString(undefined, { timeZone: "UTC" })',
],
{
signal: null,
stdout: /20\/12\/2012, 03:00:00/u,
});

spawnSyncAndExitWithoutError(
process.execPath,
[
'--locale=ko-KR',
'-p',
'new Date(Date.UTC(2012, 11, 20, 3, 0, 0)).toLocaleString(undefined, { timeZone: "UTC" })',
],
{
signal: null,
stdout: /2012\. 12\. 20\. 오전 3:00:00/u,
});

// ListFormat
spawnSyncAndExitWithoutError(
process.execPath,
[
'--locale=en-US',
'-p',
'new Intl.ListFormat(undefined, {style: "long", type:"conjunction"}).format(["a", "b", "c"])',
],
{
signal: null,
stdout: /a, b, and c/u,
});

spawnSyncAndExitWithoutError(
process.execPath,
[
'--locale=de-DE',
'-p',
'new Intl.ListFormat(undefined, {style: "long", type:"conjunction"}).format(["a", "b", "c"])',
],
{
signal: null,
stdout: /a, b und c/u,
});

// RelativeTimeFormat
spawnSyncAndExitWithoutError(
process.execPath,
[
'--locale=en',
'-p',
'new Intl.RelativeTimeFormat(undefined, { numeric: "auto" }).format(3, "quarter")',
],
{
signal: null,
stdout: /in 3 quarters/u,
});

spawnSyncAndExitWithoutError(
process.execPath,
[
'--locale=es',
'-p',
'new Intl.RelativeTimeFormat(undefined, { numeric: "auto" }).format(2, "day")',
],
{
signal: null,
stdout: /pasado mañana/u,
});

// Collator

spawnSyncAndExitWithoutError(
process.execPath,
[
'--locale=de',
'-p',
'["Z", "a", "z", "ä"].sort(new Intl.Collator().compare).join(",")',
],
{
signal: null,
stdout: /a,ä,z,Z/u,
});

spawnSyncAndExitWithoutError(
process.execPath,
[
'--locale=sv',
'-p',
'["Z", "a", "z", "ä"].sort(new Intl.Collator().compare).join(",")',
],
{
signal: null,
stdout: /a,z,Z,ä/u,
});

spawnSyncAndExitWithoutError(
process.execPath,
[
'--locale=de',
'-p',
'["Z", "a", "z", "ä"].sort(new Intl.Collator(undefined, { caseFirst: "upper" }).compare).join(",")',
],
{
signal: null,
stdout: /a,ä,Z,z/u,
});

0 comments on commit 49423d0

Please sign in to comment.