@nexim/localizer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # @nexim/localizer
2
+
3
+ ![NPM Version](https://img.shields.io/npm/v/@nexim/localizer)
4
+ ![npm bundle size](https://img.shields.io/bundlephobia/min/@nexim/localizer)
5
+ ![Build & Lint & Test](https://github.com/the-nexim/nanolib/actions/workflows/build-lint-test.yaml/badge.svg)
6
+ ![NPM Downloads](https://img.shields.io/npm/dm/@nexim/localizer)
7
+ ![NPM License](https://img.shields.io/npm/l/@nexim/localizer)
8
+
9
+ ## Overview
10
+
11
+ `@nexim/localizer` is a Lightweight i18n utilities to handle translations, number formatting, date/time localization using native browser APIs.
12
+
13
+ ## Installation
14
+
15
+ Install the package using npm or yarn:
16
+
17
+ ```sh
18
+ npm install @nexim/localizer
19
+
20
+ # Or using yarn
21
+ yarn add @nexim/localizer
22
+ ```
23
+
24
+ ## Documentation
25
+
26
+ Read full documentation [here](./docs/README.md).
package/dist/main.cjs ADDED
@@ -0,0 +1,171 @@
1
+ /* @nexim/localizer v1.0.0 */
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/main.ts
22
+ var main_exports = {};
23
+ __export(main_exports, {
24
+ Localizer: () => Localizer
25
+ });
26
+ module.exports = __toCommonJS(main_exports);
27
+ var import_unicode_digits = require("@alwatr/unicode-digits");
28
+ var import_package_tracer = require("@alwatr/package-tracer");
29
+ var import_logger = require("@alwatr/logger");
30
+ __dev_mode__: import_package_tracer.packageTracer.add("@nexim/localizer", "1.0.0");
31
+ var _Localizer = class _Localizer {
32
+ /**
33
+ * Creates a new instance of the Localizer class.
34
+ *
35
+ * @param options__ - Configuration options for localization
36
+ */
37
+ constructor(options__) {
38
+ this.options__ = options__;
39
+ this.logger_ = (0, import_logger.createLogger)("@nexim/localizer");
40
+ this.numberFormatter_ = new Intl.NumberFormat(this.options__.locale.code);
41
+ this.unicodeDigits_ = new import_unicode_digits.UnicodeDigits(this.options__.locale.language);
42
+ }
43
+ /**
44
+ * Retrieves a localized message by key from the resource dictionary.
45
+ *
46
+ * @param key - The key to lookup in the resource dictionary
47
+ * @returns The localized message string
48
+ *
49
+ * @remarks
50
+ * - Returns "\{key\}" if the key is not found in the dictionary
51
+ * - Returns an empty string if the key is null or undefined
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const localizer = new Localizer({...});
56
+ * console.log(localizer.message('hello_world')); // "Hello world!"
57
+ * console.log(localizer.message('missing_key')); // "{missing_key}"
58
+ * ```
59
+ */
60
+ message(key) {
61
+ this.logger_.logMethodArgs?.("message", key);
62
+ if (!key) return "";
63
+ const msg = this.options__.resource?.[key];
64
+ if (msg === void 0) {
65
+ this.logger_.accident("message", "l10n_key_not_found", "Key not defined in the localization resource", {
66
+ key,
67
+ locale: this.options__.locale
68
+ });
69
+ return `{${key}}`;
70
+ }
71
+ return msg;
72
+ }
73
+ /**
74
+ * Formats a number according to the current locale settings.
75
+ *
76
+ * @param number - The number to format
77
+ * @param decimal - Number of decimal places (default: 2)
78
+ * @returns Formatted number string using locale-specific formatting
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * const localizer = new Localizer({...});
83
+ * console.log(localizer.number(1234.567)); // "1,234.57"
84
+ * console.log(localizer.number(1234.567, 1)); // "1,234.6"
85
+ * ```
86
+ */
87
+ number(number, decimal = 2) {
88
+ if (number == null) return "";
89
+ decimal = Math.pow(10, decimal);
90
+ number = Math.round(number * decimal) / decimal;
91
+ return this.numberFormatter_.format(number);
92
+ }
93
+ /**
94
+ * Replaces all numeric digits in a string with their locale-specific Unicode equivalents.
95
+ *
96
+ * @param str - The input string containing numbers to replace
97
+ * @returns String with numbers converted to locale-specific digits
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * const localizer = new Localizer({locale: {code: 'fa-IR', language: 'fa'}, ...});
102
+ * console.log(localizer.replaceNumber('123')); // '۱۲۳'
103
+ * ```
104
+ */
105
+ replaceNumber(str) {
106
+ this.logger_.logMethodArgs?.("replaceNumber", str);
107
+ return this.unicodeDigits_.translate(str);
108
+ }
109
+ /**
110
+ * Formats a date according to the current locale settings.
111
+ *
112
+ * @param date - Date to format (as Date object or timestamp)
113
+ * @param options - Intl.DateTimeFormatOptions for customizing the output
114
+ * @returns Localized date string
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * const localizer = new Localizer({...});
119
+ * console.log(localizer.time(new Date())); // "4/21/2025"
120
+ * ```
121
+ */
122
+ time(date, options) {
123
+ this.logger_.logMethodArgs?.("time", { date, options });
124
+ if (typeof date === "number") date = new Date(date);
125
+ return date.toLocaleDateString(this.options__.locale.code, options);
126
+ }
127
+ /**
128
+ * Creates a relative time string comparing two dates.
129
+ *
130
+ * @param date - The date to compare (as Date object or timestamp)
131
+ * @param from - Reference date for comparison (defaults to current time)
132
+ * @param options - Formatting options for relative time
133
+ * @returns Localized relative time string
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * const localizer = new Localizer({...});
138
+ * const date = new Date(Date.now() - 3600000); // 1 hour ago
139
+ * console.log(localizer.relativeTime(date)); // "1 hour ago"
140
+ * ```
141
+ */
142
+ relativeTime(date, from = Date.now(), options = { numeric: "auto", style: "narrow" }) {
143
+ this.logger_.logMethodArgs?.("relativeTime", { date, from, options });
144
+ const rtf = new Intl.RelativeTimeFormat(this.options__.locale.code, options);
145
+ if (typeof date !== "number") date = date.getTime();
146
+ if (typeof from !== "number") from = from.getTime();
147
+ const diffSec = (from - date) / 1e3;
148
+ for (const unit of _Localizer.timeUnits_) {
149
+ const interval = Math.floor(Math.abs(diffSec / unit.seconds));
150
+ if (interval >= 1) {
151
+ return rtf.format(diffSec > 0 ? interval : -interval, unit.label);
152
+ }
153
+ }
154
+ return rtf.format(0, "second");
155
+ }
156
+ };
157
+ /**
158
+ * Time units used for relative time calculations.
159
+ * @internal
160
+ */
161
+ _Localizer.timeUnits_ = [
162
+ { label: "year", seconds: 31536e3 },
163
+ { label: "month", seconds: 2592e3 },
164
+ { label: "week", seconds: 604800 },
165
+ { label: "day", seconds: 86400 },
166
+ { label: "hour", seconds: 3600 },
167
+ { label: "minute", seconds: 60 },
168
+ { label: "second", seconds: 1 }
169
+ ];
170
+ var Localizer = _Localizer;
171
+ //# sourceMappingURL=main.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/main.ts"],
4
+ "sourcesContent": ["import { UnicodeDigits, type UnicodeLangKeys } from '@alwatr/unicode-digits';\nimport { packageTracer } from '@alwatr/package-tracer';\nimport { createLogger } from '@alwatr/logger';\n\n__dev_mode__: packageTracer.add(__package_name__, __package_version__);\n\n/**\n * Represents a locale code in the format \"language-COUNTRY\".\n * The language part must be in lowercase, and the country part must be in uppercase.\n *\n * @example \"en-US\"\n * @example \"fr-FR\"\n * @example \"de-DE\"\n */\nexport type LocaleCode = `${Lowercase<string>}-${Uppercase<string>}`;\n\n/**\n * Represents a locale configuration with language code and language identifier.\n */\nexport interface Locale {\n /**\n * fa-IR, en-US, ...\n */\n code: LocaleCode;\n\n /**\n * The ISO 639-1 language code (e.g., 'fa', 'en')\n */\n language: UnicodeLangKeys;\n}\n\n/**\n * Provides localization and internationalization functionality.\n *\n * @remarks\n * This class handles number formatting, text translation, date formatting,\n * and relative time calculations based on the specified locale.\n */\nexport class Localizer {\n /**\n * Number formatter instance for formatting numbers according to the locale.\n *\n * @internal\n */\n protected numberFormatter_;\n\n /**\n * UnicodeDigits instance for converting numbers to locale-specific digits.\n *\n * @internal\n */\n protected unicodeDigits_;\n\n protected logger_ = createLogger(__package_name__);\n\n /**\n * Time units used for relative time calculations.\n * @internal\n */\n static timeUnits_ = [\n { label: 'year', seconds: 31536000 },\n { label: 'month', seconds: 2592000 },\n { label: 'week', seconds: 604800 },\n { label: 'day', seconds: 86400 },\n { label: 'hour', seconds: 3600 },\n { label: 'minute', seconds: 60 },\n { label: 'second', seconds: 1 },\n ] as const;\n\n /**\n * Creates a new instance of the Localizer class.\n *\n * @param options__ - Configuration options for localization\n */\n constructor(private options__: { locale: Locale; resource: DictionaryReq | null }) {\n this.numberFormatter_ = new Intl.NumberFormat(this.options__.locale.code);\n this.unicodeDigits_ = new UnicodeDigits(this.options__.locale.language);\n }\n\n /**\n * Retrieves a localized message by key from the resource dictionary.\n *\n * @param key - The key to lookup in the resource dictionary\n * @returns The localized message string\n *\n * @remarks\n * - Returns \"\\{key\\}\" if the key is not found in the dictionary\n * - Returns an empty string if the key is null or undefined\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({...});\n * console.log(localizer.message('hello_world')); // \"Hello world!\"\n * console.log(localizer.message('missing_key')); // \"{missing_key}\"\n * ```\n */\n message(key: keyof NonNullable<typeof this.options__.resource>): string {\n this.logger_.logMethodArgs?.('message', key);\n if (!key) return '';\n\n const msg = this.options__.resource?.[key];\n if (msg === undefined) {\n this.logger_.accident('message', 'l10n_key_not_found', 'Key not defined in the localization resource', {\n key,\n locale: this.options__.locale,\n });\n return `{${key}}`;\n }\n // else\n return msg;\n }\n\n /**\n * Formats a number according to the current locale settings.\n *\n * @param number - The number to format\n * @param decimal - Number of decimal places (default: 2)\n * @returns Formatted number string using locale-specific formatting\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({...});\n * console.log(localizer.number(1234.567)); // \"1,234.57\"\n * console.log(localizer.number(1234.567, 1)); // \"1,234.6\"\n * ```\n */\n number(number?: number | null, decimal = 2): string {\n if (number == null) return '';\n decimal = Math.pow(10, decimal);\n number = Math.round(number * decimal) / decimal;\n return this.numberFormatter_.format(number);\n }\n\n /**\n * Replaces all numeric digits in a string with their locale-specific Unicode equivalents.\n *\n * @param str - The input string containing numbers to replace\n * @returns String with numbers converted to locale-specific digits\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({locale: {code: 'fa-IR', language: 'fa'}, ...});\n * console.log(localizer.replaceNumber('123')); // '۱۲۳'\n * ```\n */\n replaceNumber(str: string): string {\n this.logger_.logMethodArgs?.('replaceNumber', str);\n return this.unicodeDigits_.translate(str);\n }\n\n /**\n * Formats a date according to the current locale settings.\n *\n * @param date - Date to format (as Date object or timestamp)\n * @param options - Intl.DateTimeFormatOptions for customizing the output\n * @returns Localized date string\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({...});\n * console.log(localizer.time(new Date())); // \"4/21/2025\"\n * ```\n */\n time(date: number | Date, options?: Intl.DateTimeFormatOptions): string {\n this.logger_.logMethodArgs?.('time', { date, options });\n if (typeof date === 'number') date = new Date(date);\n return date.toLocaleDateString(this.options__.locale.code, options);\n }\n\n /**\n * Creates a relative time string comparing two dates.\n *\n * @param date - The date to compare (as Date object or timestamp)\n * @param from - Reference date for comparison (defaults to current time)\n * @param options - Formatting options for relative time\n * @returns Localized relative time string\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({...});\n * const date = new Date(Date.now() - 3600000); // 1 hour ago\n * console.log(localizer.relativeTime(date)); // \"1 hour ago\"\n * ```\n */\n relativeTime(\n date: number | Date,\n from: number | Date = Date.now(),\n options: Intl.RelativeTimeFormatOptions = { numeric: 'auto', style: 'narrow' },\n ): string {\n this.logger_.logMethodArgs?.('relativeTime', { date, from, options });\n\n const rtf = new Intl.RelativeTimeFormat(this.options__.locale.code, options);\n\n if (typeof date !== 'number') date = date.getTime();\n if (typeof from !== 'number') from = from.getTime();\n const diffSec = (from - date) / 1000;\n\n // Find the appropriate unit for the time difference\n for (const unit of Localizer.timeUnits_) {\n const interval = Math.floor(Math.abs(diffSec / unit.seconds));\n if (interval >= 1) {\n return rtf.format(diffSec > 0 ? interval : -interval, unit.label);\n }\n }\n\n return rtf.format(0, 'second');\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/dist/main.d.ts ADDED
@@ -0,0 +1,158 @@
1
+ import { UnicodeDigits, type UnicodeLangKeys } from '@alwatr/unicode-digits';
2
+ /**
3
+ * Represents a locale code in the format "language-COUNTRY".
4
+ * The language part must be in lowercase, and the country part must be in uppercase.
5
+ *
6
+ * @example "en-US"
7
+ * @example "fr-FR"
8
+ * @example "de-DE"
9
+ */
10
+ export type LocaleCode = `${Lowercase<string>}-${Uppercase<string>}`;
11
+ /**
12
+ * Represents a locale configuration with language code and language identifier.
13
+ */
14
+ export interface Locale {
15
+ /**
16
+ * fa-IR, en-US, ...
17
+ */
18
+ code: LocaleCode;
19
+ /**
20
+ * The ISO 639-1 language code (e.g., 'fa', 'en')
21
+ */
22
+ language: UnicodeLangKeys;
23
+ }
24
+ /**
25
+ * Provides localization and internationalization functionality.
26
+ *
27
+ * @remarks
28
+ * This class handles number formatting, text translation, date formatting,
29
+ * and relative time calculations based on the specified locale.
30
+ */
31
+ export declare class Localizer {
32
+ private options__;
33
+ /**
34
+ * Number formatter instance for formatting numbers according to the locale.
35
+ *
36
+ * @internal
37
+ */
38
+ protected numberFormatter_: Intl.NumberFormat;
39
+ /**
40
+ * UnicodeDigits instance for converting numbers to locale-specific digits.
41
+ *
42
+ * @internal
43
+ */
44
+ protected unicodeDigits_: UnicodeDigits;
45
+ protected logger_: import("@alwatr/logger").AlwatrLogger;
46
+ /**
47
+ * Time units used for relative time calculations.
48
+ * @internal
49
+ */
50
+ static timeUnits_: readonly [{
51
+ readonly label: "year";
52
+ readonly seconds: 31536000;
53
+ }, {
54
+ readonly label: "month";
55
+ readonly seconds: 2592000;
56
+ }, {
57
+ readonly label: "week";
58
+ readonly seconds: 604800;
59
+ }, {
60
+ readonly label: "day";
61
+ readonly seconds: 86400;
62
+ }, {
63
+ readonly label: "hour";
64
+ readonly seconds: 3600;
65
+ }, {
66
+ readonly label: "minute";
67
+ readonly seconds: 60;
68
+ }, {
69
+ readonly label: "second";
70
+ readonly seconds: 1;
71
+ }];
72
+ /**
73
+ * Creates a new instance of the Localizer class.
74
+ *
75
+ * @param options__ - Configuration options for localization
76
+ */
77
+ constructor(options__: {
78
+ locale: Locale;
79
+ resource: DictionaryReq | null;
80
+ });
81
+ /**
82
+ * Retrieves a localized message by key from the resource dictionary.
83
+ *
84
+ * @param key - The key to lookup in the resource dictionary
85
+ * @returns The localized message string
86
+ *
87
+ * @remarks
88
+ * - Returns "\{key\}" if the key is not found in the dictionary
89
+ * - Returns an empty string if the key is null or undefined
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * const localizer = new Localizer({...});
94
+ * console.log(localizer.message('hello_world')); // "Hello world!"
95
+ * console.log(localizer.message('missing_key')); // "{missing_key}"
96
+ * ```
97
+ */
98
+ message(key: keyof NonNullable<typeof this.options__.resource>): string;
99
+ /**
100
+ * Formats a number according to the current locale settings.
101
+ *
102
+ * @param number - The number to format
103
+ * @param decimal - Number of decimal places (default: 2)
104
+ * @returns Formatted number string using locale-specific formatting
105
+ *
106
+ * @example
107
+ * ```typescript
108
+ * const localizer = new Localizer({...});
109
+ * console.log(localizer.number(1234.567)); // "1,234.57"
110
+ * console.log(localizer.number(1234.567, 1)); // "1,234.6"
111
+ * ```
112
+ */
113
+ number(number?: number | null, decimal?: number): string;
114
+ /**
115
+ * Replaces all numeric digits in a string with their locale-specific Unicode equivalents.
116
+ *
117
+ * @param str - The input string containing numbers to replace
118
+ * @returns String with numbers converted to locale-specific digits
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * const localizer = new Localizer({locale: {code: 'fa-IR', language: 'fa'}, ...});
123
+ * console.log(localizer.replaceNumber('123')); // '۱۲۳'
124
+ * ```
125
+ */
126
+ replaceNumber(str: string): string;
127
+ /**
128
+ * Formats a date according to the current locale settings.
129
+ *
130
+ * @param date - Date to format (as Date object or timestamp)
131
+ * @param options - Intl.DateTimeFormatOptions for customizing the output
132
+ * @returns Localized date string
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * const localizer = new Localizer({...});
137
+ * console.log(localizer.time(new Date())); // "4/21/2025"
138
+ * ```
139
+ */
140
+ time(date: number | Date, options?: Intl.DateTimeFormatOptions): string;
141
+ /**
142
+ * Creates a relative time string comparing two dates.
143
+ *
144
+ * @param date - The date to compare (as Date object or timestamp)
145
+ * @param from - Reference date for comparison (defaults to current time)
146
+ * @param options - Formatting options for relative time
147
+ * @returns Localized relative time string
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * const localizer = new Localizer({...});
152
+ * const date = new Date(Date.now() - 3600000); // 1 hour ago
153
+ * console.log(localizer.relativeTime(date)); // "1 hour ago"
154
+ * ```
155
+ */
156
+ relativeTime(date: number | Date, from?: number | Date, options?: Intl.RelativeTimeFormatOptions): string;
157
+ }
158
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAM7E;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB;;OAEG;IACH,IAAI,EAAE,UAAU,CAAC;IAEjB;;OAEG;IACH,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,qBAAa,SAAS;IAoCR,OAAO,CAAC,SAAS;IAnC7B;;;;OAIG;IACH,SAAS,CAAC,gBAAgB,oBAAC;IAE3B;;;;OAIG;IACH,SAAS,CAAC,cAAc,gBAAC;IAEzB,SAAS,CAAC,OAAO,wCAAkC;IAEnD;;;OAGG;IACH,MAAM,CAAC,UAAU;;;;;;;;;;;;;;;;;;;;;OAQN;IAEX;;;;OAIG;gBACiB,SAAS,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAA;KAAE;IAKjF;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,WAAW,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,MAAM;IAgBvE;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,SAAI,GAAG,MAAM;IAOnD;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAKlC;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,qBAAqB,GAAG,MAAM;IAMvE;;;;;;;;;;;;;;OAcG;IACH,YAAY,CACV,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,IAAI,GAAE,MAAM,GAAG,IAAiB,EAChC,OAAO,GAAE,IAAI,CAAC,yBAAgE,GAC7E,MAAM;CAmBV"}
package/dist/main.mjs ADDED
@@ -0,0 +1,151 @@
1
+ /* @nexim/localizer v1.0.0 */
2
+
3
+ // src/main.ts
4
+ import { UnicodeDigits } from "@alwatr/unicode-digits";
5
+ import { packageTracer } from "@alwatr/package-tracer";
6
+ import { createLogger } from "@alwatr/logger";
7
+ __dev_mode__: packageTracer.add("@nexim/localizer", "1.0.0");
8
+ var _Localizer = class _Localizer {
9
+ /**
10
+ * Creates a new instance of the Localizer class.
11
+ *
12
+ * @param options__ - Configuration options for localization
13
+ */
14
+ constructor(options__) {
15
+ this.options__ = options__;
16
+ this.logger_ = createLogger("@nexim/localizer");
17
+ this.numberFormatter_ = new Intl.NumberFormat(this.options__.locale.code);
18
+ this.unicodeDigits_ = new UnicodeDigits(this.options__.locale.language);
19
+ }
20
+ /**
21
+ * Retrieves a localized message by key from the resource dictionary.
22
+ *
23
+ * @param key - The key to lookup in the resource dictionary
24
+ * @returns The localized message string
25
+ *
26
+ * @remarks
27
+ * - Returns "\{key\}" if the key is not found in the dictionary
28
+ * - Returns an empty string if the key is null or undefined
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const localizer = new Localizer({...});
33
+ * console.log(localizer.message('hello_world')); // "Hello world!"
34
+ * console.log(localizer.message('missing_key')); // "{missing_key}"
35
+ * ```
36
+ */
37
+ message(key) {
38
+ this.logger_.logMethodArgs?.("message", key);
39
+ if (!key) return "";
40
+ const msg = this.options__.resource?.[key];
41
+ if (msg === void 0) {
42
+ this.logger_.accident("message", "l10n_key_not_found", "Key not defined in the localization resource", {
43
+ key,
44
+ locale: this.options__.locale
45
+ });
46
+ return `{${key}}`;
47
+ }
48
+ return msg;
49
+ }
50
+ /**
51
+ * Formats a number according to the current locale settings.
52
+ *
53
+ * @param number - The number to format
54
+ * @param decimal - Number of decimal places (default: 2)
55
+ * @returns Formatted number string using locale-specific formatting
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * const localizer = new Localizer({...});
60
+ * console.log(localizer.number(1234.567)); // "1,234.57"
61
+ * console.log(localizer.number(1234.567, 1)); // "1,234.6"
62
+ * ```
63
+ */
64
+ number(number, decimal = 2) {
65
+ if (number == null) return "";
66
+ decimal = Math.pow(10, decimal);
67
+ number = Math.round(number * decimal) / decimal;
68
+ return this.numberFormatter_.format(number);
69
+ }
70
+ /**
71
+ * Replaces all numeric digits in a string with their locale-specific Unicode equivalents.
72
+ *
73
+ * @param str - The input string containing numbers to replace
74
+ * @returns String with numbers converted to locale-specific digits
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const localizer = new Localizer({locale: {code: 'fa-IR', language: 'fa'}, ...});
79
+ * console.log(localizer.replaceNumber('123')); // '۱۲۳'
80
+ * ```
81
+ */
82
+ replaceNumber(str) {
83
+ this.logger_.logMethodArgs?.("replaceNumber", str);
84
+ return this.unicodeDigits_.translate(str);
85
+ }
86
+ /**
87
+ * Formats a date according to the current locale settings.
88
+ *
89
+ * @param date - Date to format (as Date object or timestamp)
90
+ * @param options - Intl.DateTimeFormatOptions for customizing the output
91
+ * @returns Localized date string
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * const localizer = new Localizer({...});
96
+ * console.log(localizer.time(new Date())); // "4/21/2025"
97
+ * ```
98
+ */
99
+ time(date, options) {
100
+ this.logger_.logMethodArgs?.("time", { date, options });
101
+ if (typeof date === "number") date = new Date(date);
102
+ return date.toLocaleDateString(this.options__.locale.code, options);
103
+ }
104
+ /**
105
+ * Creates a relative time string comparing two dates.
106
+ *
107
+ * @param date - The date to compare (as Date object or timestamp)
108
+ * @param from - Reference date for comparison (defaults to current time)
109
+ * @param options - Formatting options for relative time
110
+ * @returns Localized relative time string
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const localizer = new Localizer({...});
115
+ * const date = new Date(Date.now() - 3600000); // 1 hour ago
116
+ * console.log(localizer.relativeTime(date)); // "1 hour ago"
117
+ * ```
118
+ */
119
+ relativeTime(date, from = Date.now(), options = { numeric: "auto", style: "narrow" }) {
120
+ this.logger_.logMethodArgs?.("relativeTime", { date, from, options });
121
+ const rtf = new Intl.RelativeTimeFormat(this.options__.locale.code, options);
122
+ if (typeof date !== "number") date = date.getTime();
123
+ if (typeof from !== "number") from = from.getTime();
124
+ const diffSec = (from - date) / 1e3;
125
+ for (const unit of _Localizer.timeUnits_) {
126
+ const interval = Math.floor(Math.abs(diffSec / unit.seconds));
127
+ if (interval >= 1) {
128
+ return rtf.format(diffSec > 0 ? interval : -interval, unit.label);
129
+ }
130
+ }
131
+ return rtf.format(0, "second");
132
+ }
133
+ };
134
+ /**
135
+ * Time units used for relative time calculations.
136
+ * @internal
137
+ */
138
+ _Localizer.timeUnits_ = [
139
+ { label: "year", seconds: 31536e3 },
140
+ { label: "month", seconds: 2592e3 },
141
+ { label: "week", seconds: 604800 },
142
+ { label: "day", seconds: 86400 },
143
+ { label: "hour", seconds: 3600 },
144
+ { label: "minute", seconds: 60 },
145
+ { label: "second", seconds: 1 }
146
+ ];
147
+ var Localizer = _Localizer;
148
+ export {
149
+ Localizer
150
+ };
151
+ //# sourceMappingURL=main.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/main.ts"],
4
+ "sourcesContent": ["import { UnicodeDigits, type UnicodeLangKeys } from '@alwatr/unicode-digits';\nimport { packageTracer } from '@alwatr/package-tracer';\nimport { createLogger } from '@alwatr/logger';\n\n__dev_mode__: packageTracer.add(__package_name__, __package_version__);\n\n/**\n * Represents a locale code in the format \"language-COUNTRY\".\n * The language part must be in lowercase, and the country part must be in uppercase.\n *\n * @example \"en-US\"\n * @example \"fr-FR\"\n * @example \"de-DE\"\n */\nexport type LocaleCode = `${Lowercase<string>}-${Uppercase<string>}`;\n\n/**\n * Represents a locale configuration with language code and language identifier.\n */\nexport interface Locale {\n /**\n * fa-IR, en-US, ...\n */\n code: LocaleCode;\n\n /**\n * The ISO 639-1 language code (e.g., 'fa', 'en')\n */\n language: UnicodeLangKeys;\n}\n\n/**\n * Provides localization and internationalization functionality.\n *\n * @remarks\n * This class handles number formatting, text translation, date formatting,\n * and relative time calculations based on the specified locale.\n */\nexport class Localizer {\n /**\n * Number formatter instance for formatting numbers according to the locale.\n *\n * @internal\n */\n protected numberFormatter_;\n\n /**\n * UnicodeDigits instance for converting numbers to locale-specific digits.\n *\n * @internal\n */\n protected unicodeDigits_;\n\n protected logger_ = createLogger(__package_name__);\n\n /**\n * Time units used for relative time calculations.\n * @internal\n */\n static timeUnits_ = [\n { label: 'year', seconds: 31536000 },\n { label: 'month', seconds: 2592000 },\n { label: 'week', seconds: 604800 },\n { label: 'day', seconds: 86400 },\n { label: 'hour', seconds: 3600 },\n { label: 'minute', seconds: 60 },\n { label: 'second', seconds: 1 },\n ] as const;\n\n /**\n * Creates a new instance of the Localizer class.\n *\n * @param options__ - Configuration options for localization\n */\n constructor(private options__: { locale: Locale; resource: DictionaryReq | null }) {\n this.numberFormatter_ = new Intl.NumberFormat(this.options__.locale.code);\n this.unicodeDigits_ = new UnicodeDigits(this.options__.locale.language);\n }\n\n /**\n * Retrieves a localized message by key from the resource dictionary.\n *\n * @param key - The key to lookup in the resource dictionary\n * @returns The localized message string\n *\n * @remarks\n * - Returns \"\\{key\\}\" if the key is not found in the dictionary\n * - Returns an empty string if the key is null or undefined\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({...});\n * console.log(localizer.message('hello_world')); // \"Hello world!\"\n * console.log(localizer.message('missing_key')); // \"{missing_key}\"\n * ```\n */\n message(key: keyof NonNullable<typeof this.options__.resource>): string {\n this.logger_.logMethodArgs?.('message', key);\n if (!key) return '';\n\n const msg = this.options__.resource?.[key];\n if (msg === undefined) {\n this.logger_.accident('message', 'l10n_key_not_found', 'Key not defined in the localization resource', {\n key,\n locale: this.options__.locale,\n });\n return `{${key}}`;\n }\n // else\n return msg;\n }\n\n /**\n * Formats a number according to the current locale settings.\n *\n * @param number - The number to format\n * @param decimal - Number of decimal places (default: 2)\n * @returns Formatted number string using locale-specific formatting\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({...});\n * console.log(localizer.number(1234.567)); // \"1,234.57\"\n * console.log(localizer.number(1234.567, 1)); // \"1,234.6\"\n * ```\n */\n number(number?: number | null, decimal = 2): string {\n if (number == null) return '';\n decimal = Math.pow(10, decimal);\n number = Math.round(number * decimal) / decimal;\n return this.numberFormatter_.format(number);\n }\n\n /**\n * Replaces all numeric digits in a string with their locale-specific Unicode equivalents.\n *\n * @param str - The input string containing numbers to replace\n * @returns String with numbers converted to locale-specific digits\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({locale: {code: 'fa-IR', language: 'fa'}, ...});\n * console.log(localizer.replaceNumber('123')); // '۱۲۳'\n * ```\n */\n replaceNumber(str: string): string {\n this.logger_.logMethodArgs?.('replaceNumber', str);\n return this.unicodeDigits_.translate(str);\n }\n\n /**\n * Formats a date according to the current locale settings.\n *\n * @param date - Date to format (as Date object or timestamp)\n * @param options - Intl.DateTimeFormatOptions for customizing the output\n * @returns Localized date string\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({...});\n * console.log(localizer.time(new Date())); // \"4/21/2025\"\n * ```\n */\n time(date: number | Date, options?: Intl.DateTimeFormatOptions): string {\n this.logger_.logMethodArgs?.('time', { date, options });\n if (typeof date === 'number') date = new Date(date);\n return date.toLocaleDateString(this.options__.locale.code, options);\n }\n\n /**\n * Creates a relative time string comparing two dates.\n *\n * @param date - The date to compare (as Date object or timestamp)\n * @param from - Reference date for comparison (defaults to current time)\n * @param options - Formatting options for relative time\n * @returns Localized relative time string\n *\n * @example\n * ```typescript\n * const localizer = new Localizer({...});\n * const date = new Date(Date.now() - 3600000); // 1 hour ago\n * console.log(localizer.relativeTime(date)); // \"1 hour ago\"\n * ```\n */\n relativeTime(\n date: number | Date,\n from: number | Date = Date.now(),\n options: Intl.RelativeTimeFormatOptions = { numeric: 'auto', style: 'narrow' },\n ): string {\n this.logger_.logMethodArgs?.('relativeTime', { date, from, options });\n\n const rtf = new Intl.RelativeTimeFormat(this.options__.locale.code, options);\n\n if (typeof date !== 'number') date = date.getTime();\n if (typeof from !== 'number') from = from.getTime();\n const diffSec = (from - date) / 1000;\n\n // Find the appropriate unit for the time difference\n for (const unit of Localizer.timeUnits_) {\n const interval = Math.floor(Math.abs(diffSec / unit.seconds));\n if (interval >= 1) {\n return rtf.format(diffSec > 0 ? interval : -interval, unit.label);\n }\n }\n\n return rtf.format(0, 'second');\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=main.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.test.d.ts","sourceRoot":"","sources":["../src/main.test.js"],"names":[],"mappings":""}
package/docs/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # @nexim/localizer
2
+
3
+ ## Classes
4
+
5
+ | Class | Description |
6
+ | --------------------------------- | ------------------------------------------------------------- |
7
+ | [Localizer](classes/Localizer.md) | Provides localization and internationalization functionality. |
8
+
9
+ ## Interfaces
10
+
11
+ | Interface | Description |
12
+ | ------------------------------ | ----------------------------------------------------------------------------- |
13
+ | [Locale](interfaces/Locale.md) | Represents a locale configuration with language code and language identifier. |
14
+
15
+ ## Type Aliases
16
+
17
+ | Type Alias | Description |
18
+ | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
19
+ | [LocaleCode](type-aliases/LocaleCode.md) | Represents a locale code in the format "language-COUNTRY". The language part must be in lowercase, and the country part must be in uppercase. |