@arikajs/localization 0.0.5
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/LICENSE +21 -0
- package/README.md +128 -0
- package/dist/Contracts/Translator.d.ts +19 -0
- package/dist/Contracts/Translator.d.ts.map +1 -0
- package/dist/Contracts/Translator.js +3 -0
- package/dist/Contracts/Translator.js.map +1 -0
- package/dist/MessageSelector.d.ts +16 -0
- package/dist/MessageSelector.d.ts.map +1 -0
- package/dist/MessageSelector.js +61 -0
- package/dist/MessageSelector.js.map +1 -0
- package/dist/Translator.d.ts +37 -0
- package/dist/Translator.d.ts.map +1 -0
- package/dist/Translator.js +94 -0
- package/dist/Translator.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ArikaJs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Arika Localization
|
|
2
|
+
|
|
3
|
+
`@arikajs/localization` provides a deep, flexible, and powerful localization system for the ArikaJS framework.
|
|
4
|
+
|
|
5
|
+
It allows your application to support multiple languages for API responses, validation messages, and views, while remaining lightweight and easy to use.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Translator } from '@arikajs/localization';
|
|
9
|
+
|
|
10
|
+
const translator = new Translator('en');
|
|
11
|
+
translator.load('en', 'auth', {
|
|
12
|
+
'welcome': 'Welcome back, :name!'
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
console.log(translator.get('auth.welcome', { name: 'Arika' }));
|
|
16
|
+
// Output: Welcome back, Arika!
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
### Status
|
|
22
|
+
|
|
23
|
+
- **Stage**: Experimental / v0.x
|
|
24
|
+
- **Scope (v0.x)**:
|
|
25
|
+
- Dot-notation key lookups
|
|
26
|
+
- Dynamic placeholder replacements
|
|
27
|
+
- Fallback locale support
|
|
28
|
+
- JSON-based translation loading
|
|
29
|
+
- Pluralization support (Advanced)
|
|
30
|
+
- JS & TS friendly API
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 🎯 Purpose
|
|
35
|
+
|
|
36
|
+
Localization is about more than just translating words; it's about providing a culturally relevant experience for your users.
|
|
37
|
+
|
|
38
|
+
This package is responsible for:
|
|
39
|
+
- Managing multiple language repositories.
|
|
40
|
+
- Resolving keys to localized strings.
|
|
41
|
+
- Handling dynamic content via placeholders.
|
|
42
|
+
- Implementing complex pluralization rules.
|
|
43
|
+
- Integrating with the ArikaJS core for seamless use in Controllers and Views.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Features
|
|
48
|
+
|
|
49
|
+
- **Dot-Notation Support**: Access nested keys easily (e.g., `validation.required`, `auth.login.success`).
|
|
50
|
+
- **Dynamic Replacements**: Use `:placeholders` to inject variable data into your translations.
|
|
51
|
+
- **Fallback Locales**: Automatically fall back to a default language if a translation is missing in the current locale.
|
|
52
|
+
- **Pluralization**: Handle singular and plural forms with complex range support (e.g., `0 items`, `1 item`, `5 items`).
|
|
53
|
+
- **JSON First**: Optimized for JSON-based translation files, making it easy to manage languages across and outside your codebase.
|
|
54
|
+
- **Type Safe**: Fully written in TypeScript with complete type definitions.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm install @arikajs/localization
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 🧩 Usage
|
|
67
|
+
|
|
68
|
+
### Basic Translation
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// Get a simple line
|
|
72
|
+
translator.get('messages.welcome');
|
|
73
|
+
|
|
74
|
+
// With replacements
|
|
75
|
+
translator.get('messages.hello', { name: 'John' }); // "Hello, John"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Pluralization (Advanced)
|
|
79
|
+
|
|
80
|
+
Pluralization allows you to define different strings based on a count. Use the `|` character to separate singular and plural forms.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
translator.load('en', 'messages', {
|
|
84
|
+
'apples': '{0} There are no apples|{1} There is one apple|[2,*] There are :count apples'
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Using the choice method
|
|
88
|
+
translator.choice('messages.apples', 0); // "There are no apples"
|
|
89
|
+
translator.choice('messages.apples', 1); // "There is one apple"
|
|
90
|
+
translator.choice('messages.apples', 5); // "There are 5 apples"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Fallback Logic
|
|
94
|
+
|
|
95
|
+
If a key doesn't exist in the current locale (e.g., `es`), the translator will automatically look for it in the fallback locale (e.g., `en`).
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 🏗 Architecture
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
localization/
|
|
103
|
+
├── src/
|
|
104
|
+
│ ├── Contracts/
|
|
105
|
+
│ │ └── Translator.ts
|
|
106
|
+
│ ├── MessageSelector.ts (Handles pluralization logic)
|
|
107
|
+
│ ├── Translator.ts (Core logic)
|
|
108
|
+
│ └── index.ts
|
|
109
|
+
├── tests/
|
|
110
|
+
├── package.json
|
|
111
|
+
├── tsconfig.json
|
|
112
|
+
└── README.md
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 🚀 Advanced Roadmap
|
|
118
|
+
|
|
119
|
+
- [ ] **Date & Number Formatting**: Localized helpers for dates, currencies, and numbers.
|
|
120
|
+
- [ ] **Validation Integration**: Automatic localization for `@arikajs/validation` rules.
|
|
121
|
+
- [ ] **React/Vue/ArkView Helpers**: Directives and hooks for frontend integration.
|
|
122
|
+
- [ ] **External API Sync**: Tooling to sync translations with services like Phrase or Lokalise.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 📜 License
|
|
127
|
+
|
|
128
|
+
`@arikajs/localization` is open-sourced software licensed under the **MIT License**.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface TranslatorInterface {
|
|
2
|
+
/**
|
|
3
|
+
* Get the translation for the given key.
|
|
4
|
+
*/
|
|
5
|
+
get(key: string, replace?: Record<string, any>, locale?: string | null): string;
|
|
6
|
+
/**
|
|
7
|
+
* Get a translation according to an integer value (pluralization).
|
|
8
|
+
*/
|
|
9
|
+
choice(key: string, number: number, replace?: Record<string, any>, locale?: string | null): string;
|
|
10
|
+
/**
|
|
11
|
+
* Get the current locale.
|
|
12
|
+
*/
|
|
13
|
+
getLocale(): string;
|
|
14
|
+
/**
|
|
15
|
+
* Set the current locale.
|
|
16
|
+
*/
|
|
17
|
+
setLocale(locale: string): void;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=Translator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Translator.d.ts","sourceRoot":"","sources":["../../src/Contracts/Translator.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAChC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC;IAEhF;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC;IAEnG;;OAEG;IACH,SAAS,IAAI,MAAM,CAAC;IAEpB;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Translator.js","sourceRoot":"","sources":["../../src/Contracts/Translator.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class MessageSelector {
|
|
2
|
+
/**
|
|
3
|
+
* Select a proper message from a message line based on a given number.
|
|
4
|
+
*/
|
|
5
|
+
choose(line: string, number: number, locale: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Get the index for the plural forms.
|
|
8
|
+
* Simplistic version: 0 if number is 1, else 1.
|
|
9
|
+
*/
|
|
10
|
+
private getPluralIndex;
|
|
11
|
+
/**
|
|
12
|
+
* Check if a segment matches a specific range.
|
|
13
|
+
*/
|
|
14
|
+
private getRangeMatch;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=MessageSelector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageSelector.d.ts","sourceRoot":"","sources":["../src/MessageSelector.ts"],"names":[],"mappings":"AAAA,qBAAa,eAAe;IACxB;;OAEG;IACI,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAqBnE;;;OAGG;IACH,OAAO,CAAC,cAAc;IAKtB;;OAEG;IACH,OAAO,CAAC,aAAa;CA4BxB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MessageSelector = void 0;
|
|
4
|
+
class MessageSelector {
|
|
5
|
+
/**
|
|
6
|
+
* Select a proper message from a message line based on a given number.
|
|
7
|
+
*/
|
|
8
|
+
choose(line, number, locale) {
|
|
9
|
+
const segments = line.split('|');
|
|
10
|
+
// Check for specific range matches like {0} or [2,5]
|
|
11
|
+
for (const segment of segments) {
|
|
12
|
+
const match = this.getRangeMatch(segment, number);
|
|
13
|
+
if (match !== null) {
|
|
14
|
+
return match.trim();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Default to standard pluralization (first segment is singular, second is plural)
|
|
18
|
+
const pluralIndex = this.getPluralIndex(number, locale);
|
|
19
|
+
if (segments.length === 1) {
|
|
20
|
+
return segments[0].trim();
|
|
21
|
+
}
|
|
22
|
+
return (segments[pluralIndex] || segments[0]).trim();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get the index for the plural forms.
|
|
26
|
+
* Simplistic version: 0 if number is 1, else 1.
|
|
27
|
+
*/
|
|
28
|
+
getPluralIndex(number, locale) {
|
|
29
|
+
// Most languages use 0 for singular (1) and 1 for plural (not 1)
|
|
30
|
+
return number === 1 ? 0 : 1;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if a segment matches a specific range.
|
|
34
|
+
*/
|
|
35
|
+
getRangeMatch(segment, number) {
|
|
36
|
+
const match = segment.match(/^[\{\[]([^\}\]]+)[\}\]](.*)/s);
|
|
37
|
+
if (!match) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const condition = match[1];
|
|
41
|
+
const text = match[2];
|
|
42
|
+
if (condition.includes(',')) {
|
|
43
|
+
const [from, to] = condition.split(',').map(s => s.trim());
|
|
44
|
+
if (to === '*' && number >= parseInt(from)) {
|
|
45
|
+
return text;
|
|
46
|
+
}
|
|
47
|
+
else if (from === '*' && number <= parseInt(to)) {
|
|
48
|
+
return text;
|
|
49
|
+
}
|
|
50
|
+
else if (number >= parseInt(from) && number <= parseInt(to)) {
|
|
51
|
+
return text;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (parseInt(condition) === number) {
|
|
55
|
+
return text;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.MessageSelector = MessageSelector;
|
|
61
|
+
//# sourceMappingURL=MessageSelector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageSelector.js","sourceRoot":"","sources":["../src/MessageSelector.ts"],"names":[],"mappings":";;;AAAA,MAAa,eAAe;IACxB;;OAEG;IACI,MAAM,CAAC,IAAY,EAAE,MAAc,EAAE,MAAc;QACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAEjC,qDAAqD;QACrD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAClD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACjB,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;YACxB,CAAC;QACL,CAAC;QAED,kFAAkF;QAClF,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAExD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9B,CAAC;QAED,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzD,CAAC;IAED;;;OAGG;IACK,cAAc,CAAC,MAAc,EAAE,MAAc;QACjD,iEAAiE;QACjE,OAAO,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChC,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,OAAe,EAAE,MAAc;QACjD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAE5D,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAEtB,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YAE3D,IAAI,EAAE,KAAK,GAAG,IAAI,MAAM,IAAI,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzC,OAAO,IAAI,CAAC;YAChB,CAAC;iBAAM,IAAI,IAAI,KAAK,GAAG,IAAI,MAAM,IAAI,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;gBAChD,OAAO,IAAI,CAAC;YAChB,CAAC;iBAAM,IAAI,MAAM,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,MAAM,IAAI,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;gBAC5D,OAAO,IAAI,CAAC;YAChB,CAAC;QACL,CAAC;QAED,IAAI,QAAQ,CAAC,SAAS,CAAC,KAAK,MAAM,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ;AAjED,0CAiEC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { TranslatorInterface } from './Contracts/Translator';
|
|
2
|
+
export declare class Translator implements TranslatorInterface {
|
|
3
|
+
private locale;
|
|
4
|
+
private fallbackLocale;
|
|
5
|
+
private translations;
|
|
6
|
+
private selector;
|
|
7
|
+
constructor(locale?: string, fallbackLocale?: string);
|
|
8
|
+
/**
|
|
9
|
+
* Get the translation for the given key.
|
|
10
|
+
*/
|
|
11
|
+
get(key: string, replace?: Record<string, any>, locale?: string | null): string;
|
|
12
|
+
/**
|
|
13
|
+
* Get a translation according to an integer value.
|
|
14
|
+
*/
|
|
15
|
+
choice(key: string, number: number, replace?: Record<string, any>, locale?: string | null): string;
|
|
16
|
+
/**
|
|
17
|
+
* Set the current locale.
|
|
18
|
+
*/
|
|
19
|
+
setLocale(locale: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Get the current locale.
|
|
22
|
+
*/
|
|
23
|
+
getLocale(): string;
|
|
24
|
+
/**
|
|
25
|
+
* Load translations into the translator.
|
|
26
|
+
*/
|
|
27
|
+
load(locale: string, group: string, lines: Record<string, any>): void;
|
|
28
|
+
/**
|
|
29
|
+
* Retrieve a line from the translation repository.
|
|
30
|
+
*/
|
|
31
|
+
private getLine;
|
|
32
|
+
/**
|
|
33
|
+
* Make the place-holder replacements in the given line.
|
|
34
|
+
*/
|
|
35
|
+
private makeReplacements;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=Translator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Translator.d.ts","sourceRoot":"","sources":["../src/Translator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAG7D,qBAAa,UAAW,YAAW,mBAAmB;IAClD,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,QAAQ,CAAkB;gBAEtB,MAAM,GAAE,MAAa,EAAE,cAAc,GAAE,MAAa;IAMhE;;OAEG;IACI,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EAAE,MAAM,GAAE,MAAM,GAAG,IAAW,GAAG,MAAM;IAiBhG;;OAEG;IACI,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EAAE,MAAM,GAAE,MAAM,GAAG,IAAW,GAAG,MAAM;IAanH;;OAEG;IACI,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAKtC;;OAEG;IACI,SAAS,IAAI,MAAM;IAI1B;;OAEG;IACI,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAO5E;;OAEG;IACH,OAAO,CAAC,OAAO;IAmBf;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAW3B"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Translator = void 0;
|
|
4
|
+
const MessageSelector_1 = require("./MessageSelector");
|
|
5
|
+
class Translator {
|
|
6
|
+
constructor(locale = 'en', fallbackLocale = 'en') {
|
|
7
|
+
this.locale = 'en';
|
|
8
|
+
this.fallbackLocale = 'en';
|
|
9
|
+
this.translations = {};
|
|
10
|
+
this.locale = locale;
|
|
11
|
+
this.fallbackLocale = fallbackLocale;
|
|
12
|
+
this.selector = new MessageSelector_1.MessageSelector();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get the translation for the given key.
|
|
16
|
+
*/
|
|
17
|
+
get(key, replace = {}, locale = null) {
|
|
18
|
+
const currentLocale = locale || this.locale;
|
|
19
|
+
let line = this.getLine(currentLocale, key);
|
|
20
|
+
// Fallback if not found
|
|
21
|
+
if (!line && currentLocale !== this.fallbackLocale) {
|
|
22
|
+
line = this.getLine(this.fallbackLocale, key);
|
|
23
|
+
}
|
|
24
|
+
if (!line) {
|
|
25
|
+
return key;
|
|
26
|
+
}
|
|
27
|
+
return this.makeReplacements(line, replace);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get a translation according to an integer value.
|
|
31
|
+
*/
|
|
32
|
+
choice(key, number, replace = {}, locale = null) {
|
|
33
|
+
const line = this.get(key, replace, locale);
|
|
34
|
+
// If the line is the key itself (not found), return it
|
|
35
|
+
if (line === key) {
|
|
36
|
+
return key;
|
|
37
|
+
}
|
|
38
|
+
const selected = this.selector.choose(line, number, locale || this.locale);
|
|
39
|
+
return this.makeReplacements(selected, { ...replace, count: number });
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Set the current locale.
|
|
43
|
+
*/
|
|
44
|
+
setLocale(locale) {
|
|
45
|
+
this.locale = locale;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the current locale.
|
|
49
|
+
*/
|
|
50
|
+
getLocale() {
|
|
51
|
+
return this.locale;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Load translations into the translator.
|
|
55
|
+
*/
|
|
56
|
+
load(locale, group, lines) {
|
|
57
|
+
if (!this.translations[locale]) {
|
|
58
|
+
this.translations[locale] = {};
|
|
59
|
+
}
|
|
60
|
+
this.translations[locale][group] = lines;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Retrieve a line from the translation repository.
|
|
64
|
+
*/
|
|
65
|
+
getLine(locale, key) {
|
|
66
|
+
const segments = key.split('.');
|
|
67
|
+
const group = segments.shift();
|
|
68
|
+
if (!group || !this.translations[locale] || !this.translations[locale][group]) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
let line = this.translations[locale][group];
|
|
72
|
+
for (const segment of segments) {
|
|
73
|
+
if (line[segment] === undefined) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
line = line[segment];
|
|
77
|
+
}
|
|
78
|
+
return typeof line === 'string' ? line : null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Make the place-holder replacements in the given line.
|
|
82
|
+
*/
|
|
83
|
+
makeReplacements(line, replace) {
|
|
84
|
+
if (Object.keys(replace).length === 0) {
|
|
85
|
+
return line;
|
|
86
|
+
}
|
|
87
|
+
Object.entries(replace).forEach(([key, value]) => {
|
|
88
|
+
line = line.replace(new RegExp(`:${key}`, 'g'), String(value));
|
|
89
|
+
});
|
|
90
|
+
return line;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.Translator = Translator;
|
|
94
|
+
//# sourceMappingURL=Translator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Translator.js","sourceRoot":"","sources":["../src/Translator.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./Translator"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA6B"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arikajs/localization",
|
|
3
|
+
"version": "0.0.5",
|
|
4
|
+
"description": "Flexible and powerful localization system for ArikaJS",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"prepublishOnly": "npm run build",
|
|
14
|
+
"dev": "tsc -p tsconfig.json --watch"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"arikajs",
|
|
18
|
+
"localization",
|
|
19
|
+
"translation",
|
|
20
|
+
"i18n",
|
|
21
|
+
"l10n"
|
|
22
|
+
],
|
|
23
|
+
"author": "ArikaJs",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"homepage": "https://github.com/ArikaJs/arikajs/tree/main/packages/localization#readme",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/ArikaJs/arikajs.git",
|
|
29
|
+
"directory": "packages/localization"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/ArikaJs/arikajs/issues"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@arikajs/foundation": "*"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/jest": "^29.5.12",
|
|
39
|
+
"@types/node": "^20.11.24",
|
|
40
|
+
"jest": "^29.7.0",
|
|
41
|
+
"ts-jest": "^29.1.2",
|
|
42
|
+
"typescript": "^5.3.3"
|
|
43
|
+
}
|
|
44
|
+
}
|