canopy-i18n 0.1.1 → 0.2.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 +312 -120
- package/dist/bindLocale.d.ts +10 -0
- package/dist/{applyLocale.js → bindLocale.js} +11 -3
- package/dist/chainBuilder.d.ts +29 -0
- package/dist/chainBuilder.js +84 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +7 -5
- package/dist/message.d.ts +3 -6
- package/dist/message.js +7 -18
- package/dist/types.d.ts +1 -0
- package/dist/types.js +4 -0
- package/package.json +14 -13
- package/dist/applyLocale.d.ts +0 -1
- package/dist/applyLocaleDeep.d.ts +0 -1
- package/dist/applyLocaleDeep.js +0 -24
- package/dist/builder.d.ts +0 -6
- package/dist/builder.js +0 -10
- package/dist/createBuilder.d.ts +0 -29
- package/dist/createBuilder.js +0 -70
- package/dist/createI18n.d.ts +0 -6
- package/dist/createI18n.js +0 -10
- package/dist/test.d.ts +0 -1
- package/dist/test.js +0 -12
- package/dist/testData.d.ts +0 -12
- package/dist/testData.js +0 -27
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
# canopy-i18n
|
|
2
2
|
|
|
3
|
-
A tiny, type-safe i18n
|
|
3
|
+
A tiny, type-safe i18n library for building localized messages with builder pattern and applying locales across nested data structures.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Features
|
|
6
6
|
- **Type-safe locales**: Compile-time safety for allowed locale keys.
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
9
|
-
- **Flexible templating**:
|
|
10
|
-
- **Deep locale application**: Switch locale across entire object/array trees.
|
|
7
|
+
- **Builder pattern**: Chain methods to build multiple messages at once.
|
|
8
|
+
- **String or template functions**: Use plain strings or `(ctx) => string` templates.
|
|
9
|
+
- **Flexible templating**: Templates are plain functions, so you can freely use JavaScript template literals, conditionals, helpers, or any formatting library.
|
|
10
|
+
- **Deep locale application**: Switch locale across entire object/array trees, including nested builders.
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
@@ -17,179 +17,371 @@ npm install canopy-i18n
|
|
|
17
17
|
pnpm add canopy-i18n
|
|
18
18
|
# or
|
|
19
19
|
yarn add canopy-i18n
|
|
20
|
+
# or
|
|
21
|
+
bun add canopy-i18n
|
|
20
22
|
```
|
|
21
23
|
|
|
22
|
-
## Quick
|
|
24
|
+
## Quick Start
|
|
23
25
|
|
|
24
26
|
```ts
|
|
25
|
-
import { createI18n,
|
|
26
|
-
|
|
27
|
-
// 1)
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
// 2) Define messages
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
console.log(
|
|
27
|
+
import { createI18n, bindLocale } from 'canopy-i18n';
|
|
28
|
+
|
|
29
|
+
// 1) Create a builder with allowed locales
|
|
30
|
+
const baseBuilder = createI18n(['ja', 'en'] as const);
|
|
31
|
+
|
|
32
|
+
// 2) Define messages using method chaining (store in a variable)
|
|
33
|
+
const builder = baseBuilder
|
|
34
|
+
.add({
|
|
35
|
+
title: {
|
|
36
|
+
ja: 'タイトルテスト',
|
|
37
|
+
en: 'Title Test',
|
|
38
|
+
},
|
|
39
|
+
greeting: {
|
|
40
|
+
ja: 'こんにちは',
|
|
41
|
+
en: 'Hello',
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
.addTemplates<{ name: string; age: number }>()({
|
|
45
|
+
welcome: {
|
|
46
|
+
ja: (ctx) => `こんにちは、${ctx.name}さん。あなたは${ctx.age}歳です。`,
|
|
47
|
+
en: (ctx) => `Hello, ${ctx.name}. You are ${ctx.age} years old.`,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// 3) Reuse the builder to create messages for different locales
|
|
52
|
+
const enMessages = builder.build('en');
|
|
53
|
+
const jaMessages = builder.build('ja');
|
|
54
|
+
|
|
55
|
+
// 4) Render messages (English)
|
|
56
|
+
console.log(enMessages.title.render()); // "Title Test"
|
|
57
|
+
console.log(enMessages.greeting.render()); // "Hello"
|
|
58
|
+
console.log(enMessages.welcome.render({ name: 'Tanaka', age: 20 })); // "Hello, Tanaka. You are 20 years old."
|
|
59
|
+
|
|
60
|
+
// 5) Render messages (Japanese)
|
|
61
|
+
console.log(jaMessages.title.render()); // "タイトルテスト"
|
|
62
|
+
console.log(jaMessages.greeting.render()); // "こんにちは"
|
|
63
|
+
console.log(jaMessages.welcome.render({ name: 'Tanaka', age: 20 })); // "こんにちは、Tanakaさん。あなたは20歳です。"
|
|
55
64
|
```
|
|
56
65
|
|
|
57
66
|
## API
|
|
58
67
|
|
|
59
|
-
### createI18n(locales
|
|
60
|
-
|
|
68
|
+
### `createI18n(locales)`
|
|
69
|
+
Creates a `ChainBuilder` instance to build localized messages.
|
|
61
70
|
|
|
62
71
|
- **locales**: `readonly string[]` — Allowed locale keys (e.g. `['ja', 'en'] as const`).
|
|
63
|
-
-
|
|
72
|
+
- Returns: `ChainBuilder<Locales, {}>` — A builder instance to chain message definitions.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const builder = createI18n(['ja', 'en', 'fr'] as const);
|
|
76
|
+
```
|
|
64
77
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
- `defineMessage<Record<L[number], Template<C>>>() -> I18nMessage<L, C>`
|
|
78
|
+
### `ChainBuilder`
|
|
79
|
+
A builder class for creating multiple localized messages with method chaining.
|
|
68
80
|
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
#### `.add(entries)`
|
|
82
|
+
Adds multiple string messages at once.
|
|
71
83
|
|
|
72
|
-
- **
|
|
73
|
-
|
|
74
|
-
- `locale: L[number]` (getter)
|
|
75
|
-
- `fallbackLocale: L[number]` (getter)
|
|
76
|
-
- `data: Record<L[number], Template<C>>`
|
|
77
|
-
- **methods**
|
|
78
|
-
- `setLocale(locale: L[number]): this`
|
|
79
|
-
- `setFallbackLocale(locale: L[number]): this`
|
|
80
|
-
- `render(ctx?: C): string` — If the value for the active locale is a function, it’s invoked with `ctx`; otherwise the string is returned. Falls back to `fallbackLocale` if needed.
|
|
84
|
+
- **entries**: `Record<string, Record<Locale, string>>`
|
|
85
|
+
- Returns: `ChainBuilder` with added messages
|
|
81
86
|
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
```ts
|
|
88
|
+
const builder = createI18n(['ja', 'en'] as const)
|
|
89
|
+
.add({
|
|
90
|
+
title: { ja: 'タイトル', en: 'Title' },
|
|
91
|
+
greeting: { ja: 'こんにちは', en: 'Hello' },
|
|
92
|
+
});
|
|
93
|
+
```
|
|
84
94
|
|
|
85
|
-
|
|
95
|
+
#### `.addTemplates<Context>()(entries)`
|
|
96
|
+
Adds multiple template function messages at once with a unified context type.
|
|
86
97
|
|
|
87
|
-
|
|
98
|
+
Note: This uses a curried API for better type inference. Call `addTemplates<Context>()` first, then call the returned function with entries.
|
|
99
|
+
|
|
100
|
+
- **Context**: Type parameter for the template function context
|
|
101
|
+
- **entries**: `Record<string, Record<Locale, (ctx: Context) => string>>`
|
|
102
|
+
- Returns: `ChainBuilder` with added template messages
|
|
88
103
|
|
|
89
104
|
```ts
|
|
90
|
-
|
|
105
|
+
const builder = createI18n(['ja', 'en'] as const)
|
|
106
|
+
.addTemplates<{ name: string; age: number }>()({
|
|
107
|
+
greet: {
|
|
108
|
+
ja: (ctx) => `こんにちは、${ctx.name}さん。${ctx.age}歳ですね。`,
|
|
109
|
+
en: (ctx) => `Hello, ${ctx.name}. You are ${ctx.age}.`,
|
|
110
|
+
},
|
|
111
|
+
farewell: {
|
|
112
|
+
ja: (ctx) => `さようなら、${ctx.name}さん。`,
|
|
113
|
+
en: (ctx) => `Goodbye, ${ctx.name}.`,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
91
116
|
```
|
|
92
117
|
|
|
93
|
-
|
|
118
|
+
#### `.build(locale?)`
|
|
119
|
+
Builds the final messages object.
|
|
120
|
+
|
|
121
|
+
- **locale**: (optional) `Locale` — If provided, sets this locale on all messages before returning. If omitted, uses the first locale in the locales array as default.
|
|
122
|
+
- Returns: `Messages` — An object containing all defined messages
|
|
94
123
|
|
|
95
124
|
```ts
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
125
|
+
// Build with default locale (first in array)
|
|
126
|
+
const defaultMessages = builder.build();
|
|
127
|
+
|
|
128
|
+
// Build with specific locale
|
|
129
|
+
const englishMessages = builder.build('en');
|
|
130
|
+
const japaneseMessages = builder.build('ja');
|
|
101
131
|
```
|
|
102
132
|
|
|
103
|
-
|
|
104
|
-
- CommonJS build (`main: dist/index.js`) with TypeScript type declarations (`types: dist/index.d.ts`).
|
|
105
|
-
- Works in Node or bundlers; recommended usage is TypeScript/ESM import via your build tool.
|
|
106
|
-
- License: MIT.
|
|
107
|
-
- Not a tagged template library: you write plain functions (examples use JS template literals inside those functions).
|
|
133
|
+
**Note**: `build(locale)` creates a deep clone and does not mutate the builder instance, allowing you to build multiple locale versions from the same builder.
|
|
108
134
|
|
|
109
|
-
|
|
135
|
+
#### `.clone()`
|
|
136
|
+
Creates an independent copy of the current builder with all its messages.
|
|
110
137
|
|
|
111
|
-
|
|
138
|
+
- Returns: `ChainBuilder<Locales, Messages>` — A new builder instance with cloned messages
|
|
112
139
|
|
|
113
140
|
```ts
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
141
|
+
const builder1 = createI18n(['ja', 'en'] as const)
|
|
142
|
+
.add({
|
|
143
|
+
title: { ja: 'タイトル', en: 'Title' },
|
|
144
|
+
});
|
|
117
145
|
|
|
118
|
-
|
|
119
|
-
ja: '
|
|
120
|
-
en: 'Title Test',
|
|
146
|
+
const builder2 = builder1.clone().add({
|
|
147
|
+
greeting: { ja: 'こんにちは', en: 'Hello' },
|
|
121
148
|
});
|
|
122
149
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
150
|
+
const messages1 = builder1.build('ja');
|
|
151
|
+
const messages2 = builder2.build('ja');
|
|
152
|
+
|
|
153
|
+
console.log(messages1.title.render()); // "タイトル"
|
|
154
|
+
console.log(messages1.greeting); // undefined
|
|
155
|
+
|
|
156
|
+
console.log(messages2.title.render()); // "タイトル"
|
|
157
|
+
console.log(messages2.greeting.render()); // "こんにちは"
|
|
127
158
|
```
|
|
128
159
|
|
|
160
|
+
**Note**: `clone()` creates a deep copy of all messages, allowing you to branch off from a base builder and add different messages independently.
|
|
161
|
+
|
|
162
|
+
### `I18nMessage<Locales, Context>`
|
|
163
|
+
Represents a single localized message.
|
|
164
|
+
|
|
165
|
+
#### Properties
|
|
166
|
+
- `locales: Locales` — Readonly array of allowed locales
|
|
167
|
+
- `locale: Locale` — Current active locale (getter)
|
|
168
|
+
- `data: Record<Locale, Template<Context>>` — Message data for all locales (getter)
|
|
169
|
+
|
|
170
|
+
#### Methods
|
|
171
|
+
- `setLocale(locale: Locale): this` — Sets the active locale
|
|
172
|
+
- `setData(data: Record<Locale, Template<Context>>): this` — Sets the message data
|
|
173
|
+
- `render(ctx?: Context): string` — Renders the message for the active locale
|
|
174
|
+
|
|
129
175
|
```ts
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
176
|
+
const message = messages.title;
|
|
177
|
+
console.log(message.locale); // Current locale
|
|
178
|
+
console.log(message.render()); // Rendered string
|
|
133
179
|
|
|
134
|
-
|
|
180
|
+
message.setLocale('ja');
|
|
181
|
+
console.log(message.render()); // Japanese version
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### `bindLocale(obj, locale)`
|
|
185
|
+
Recursively traverses objects/arrays and sets the given locale on all `I18nMessage` instances and builds all `ChainBuilder` instances encountered.
|
|
186
|
+
|
|
187
|
+
- **obj**: Any object/array structure containing messages or builders
|
|
188
|
+
- **locale**: The locale to apply
|
|
189
|
+
- Returns: A new structure with locale applied (containers are cloned, message instances are updated in place)
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
const data = {
|
|
193
|
+
common: builder1,
|
|
194
|
+
nested: {
|
|
195
|
+
special: builder2,
|
|
196
|
+
},
|
|
197
|
+
};
|
|
135
198
|
|
|
136
|
-
|
|
137
|
-
console.log(
|
|
199
|
+
const localized = bindLocale(data, 'en');
|
|
200
|
+
console.log(localized.common.title.render()); // English version
|
|
201
|
+
console.log(localized.nested.special.msg.render()); // English version
|
|
138
202
|
```
|
|
139
203
|
|
|
140
|
-
|
|
204
|
+
**Note**: `bindLocale` works with both `ChainBuilder` instances (automatically building them with the specified locale) and already-built message objects (updating their locale).
|
|
205
|
+
|
|
206
|
+
## Types
|
|
141
207
|
|
|
142
208
|
```ts
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
export const defineMessage = createI18n(['ja', 'en'] as const, 'ja');
|
|
209
|
+
export type Template<C> = string | ((ctx: C) => string);
|
|
210
|
+
export type LocalizedMessage<Locales, Context> = I18nMessage<Locales, Context>;
|
|
146
211
|
```
|
|
147
212
|
|
|
213
|
+
## Exports
|
|
214
|
+
|
|
148
215
|
```ts
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
export
|
|
216
|
+
export { createI18n, ChainBuilder } from 'canopy-i18n';
|
|
217
|
+
export { I18nMessage, isI18nMessage } from 'canopy-i18n';
|
|
218
|
+
export { bindLocale, isChainBuilder } from 'canopy-i18n';
|
|
219
|
+
export type { Template, LocalizedMessage } from 'canopy-i18n';
|
|
152
220
|
```
|
|
153
221
|
|
|
222
|
+
## Usage Patterns
|
|
223
|
+
|
|
224
|
+
### Basic String Messages
|
|
225
|
+
|
|
154
226
|
```ts
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
227
|
+
const messages = createI18n(['ja', 'en'] as const)
|
|
228
|
+
.add({
|
|
229
|
+
title: { ja: 'タイトル', en: 'Title' },
|
|
230
|
+
greeting: { ja: 'こんにちは', en: 'Hello' },
|
|
231
|
+
farewell: { ja: 'さようなら', en: 'Goodbye' },
|
|
232
|
+
})
|
|
233
|
+
.build('en');
|
|
234
|
+
|
|
235
|
+
console.log(messages.title.render()); // "Title"
|
|
236
|
+
console.log(messages.greeting.render()); // "Hello"
|
|
158
237
|
```
|
|
159
238
|
|
|
239
|
+
### Template Functions with Context
|
|
240
|
+
|
|
160
241
|
```ts
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
242
|
+
const messages = createI18n(['ja', 'en'] as const)
|
|
243
|
+
.addTemplates<{ name: string; age: number }>()({
|
|
244
|
+
profile: {
|
|
245
|
+
ja: (ctx) => `名前: ${ctx.name}、年齢: ${ctx.age}歳`,
|
|
246
|
+
en: (ctx) => `Name: ${ctx.name}, Age: ${ctx.age}`,
|
|
247
|
+
},
|
|
248
|
+
})
|
|
249
|
+
.build('en');
|
|
250
|
+
|
|
251
|
+
console.log(messages.profile.render({ name: 'Taro', age: 25 }));
|
|
252
|
+
// "Name: Taro, Age: 25"
|
|
164
253
|
```
|
|
165
254
|
|
|
255
|
+
### Mixing String and Template Messages
|
|
256
|
+
|
|
166
257
|
```ts
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
258
|
+
const messages = createI18n(['ja', 'en'] as const)
|
|
259
|
+
.add({
|
|
260
|
+
title: { ja: 'タイトル', en: 'Title' },
|
|
261
|
+
})
|
|
262
|
+
.addTemplates<{ count: number }>()({
|
|
263
|
+
items: {
|
|
264
|
+
ja: (ctx) => `${ctx.count}個のアイテム`,
|
|
265
|
+
en: (ctx) => `${ctx.count} items`,
|
|
266
|
+
},
|
|
267
|
+
})
|
|
268
|
+
.build('ja');
|
|
269
|
+
|
|
270
|
+
console.log(messages.title.render()); // "タイトル"
|
|
271
|
+
console.log(messages.items.render({ count: 5 })); // "5個のアイテム"
|
|
272
|
+
```
|
|
170
273
|
|
|
171
|
-
|
|
274
|
+
### Using Clone for Shared Base Messages
|
|
172
275
|
|
|
173
|
-
|
|
174
|
-
|
|
276
|
+
```ts
|
|
277
|
+
// Create a base builder with common messages
|
|
278
|
+
const baseBuilder = createI18n(['ja', 'en'] as const)
|
|
279
|
+
.add({
|
|
280
|
+
common: { ja: '共通', en: 'Common' },
|
|
281
|
+
error: { ja: 'エラー', en: 'Error' },
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Clone and extend for admin pages
|
|
285
|
+
const adminMessages = baseBuilder.clone()
|
|
286
|
+
.add({
|
|
287
|
+
adminTitle: { ja: '管理画面', en: 'Admin Panel' },
|
|
288
|
+
})
|
|
289
|
+
.build('en');
|
|
290
|
+
|
|
291
|
+
// Clone and extend for user pages
|
|
292
|
+
const userMessages = baseBuilder.clone()
|
|
293
|
+
.add({
|
|
294
|
+
userTitle: { ja: 'ユーザー画面', en: 'User Panel' },
|
|
295
|
+
})
|
|
296
|
+
.build('en');
|
|
297
|
+
|
|
298
|
+
console.log(adminMessages.common.render()); // "Common"
|
|
299
|
+
console.log(adminMessages.adminTitle.render()); // "Admin Panel"
|
|
300
|
+
console.log(userMessages.common.render()); // "Common"
|
|
301
|
+
console.log(userMessages.userTitle.render()); // "User Panel"
|
|
175
302
|
```
|
|
176
303
|
|
|
177
|
-
|
|
304
|
+
### Namespace Pattern (Split Files)
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
// i18n/locales.ts
|
|
308
|
+
export const LOCALES = ['ja', 'en'] as const;
|
|
309
|
+
|
|
310
|
+
// i18n/common.ts
|
|
311
|
+
import { createI18n } from 'canopy-i18n';
|
|
312
|
+
import { LOCALES } from './locales';
|
|
313
|
+
|
|
314
|
+
export const common = createI18n(LOCALES).add({
|
|
315
|
+
hello: { ja: 'こんにちは', en: 'Hello' },
|
|
316
|
+
goodbye: { ja: 'さようなら', en: 'Goodbye' },
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// i18n/user.ts
|
|
320
|
+
import { createI18n } from 'canopy-i18n';
|
|
321
|
+
import { LOCALES } from './locales';
|
|
178
322
|
|
|
179
|
-
|
|
323
|
+
export const user = createI18n(LOCALES).addTemplates<{ name: string }>()({
|
|
324
|
+
welcome: {
|
|
325
|
+
ja: (ctx) => `ようこそ、${ctx.name}さん`,
|
|
326
|
+
en: (ctx) => `Welcome, ${ctx.name}`,
|
|
327
|
+
},
|
|
328
|
+
});
|
|
180
329
|
|
|
181
|
-
|
|
330
|
+
// i18n/index.ts
|
|
331
|
+
export { common } from './common';
|
|
332
|
+
export { user } from './user';
|
|
182
333
|
|
|
183
|
-
|
|
184
|
-
|
|
334
|
+
// app.ts
|
|
335
|
+
import { bindLocale } from 'canopy-i18n';
|
|
336
|
+
import * as i18n from './i18n';
|
|
185
337
|
|
|
186
|
-
|
|
338
|
+
const messages = bindLocale(i18n, 'en');
|
|
339
|
+
console.log(messages.common.hello.render()); // "Hello"
|
|
340
|
+
console.log(messages.user.welcome.render({ name: 'John' })); // "Welcome, John"
|
|
341
|
+
```
|
|
187
342
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
343
|
+
### Dynamic Locale Switching
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
const builder = createI18n(['ja', 'en'] as const)
|
|
347
|
+
.add({
|
|
348
|
+
title: { ja: 'タイトル', en: 'Title' },
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Build different locale versions from the same builder
|
|
352
|
+
const jaMessages = builder.build('ja');
|
|
353
|
+
const enMessages = builder.build('en');
|
|
354
|
+
|
|
355
|
+
console.log(jaMessages.title.render()); // "タイトル"
|
|
356
|
+
console.log(enMessages.title.render()); // "Title"
|
|
357
|
+
|
|
358
|
+
// Or use bindLocale to switch locale dynamically
|
|
359
|
+
const messages = builder.build();
|
|
360
|
+
const localizedJa = bindLocale(messages, 'ja');
|
|
361
|
+
const localizedEn = bindLocale(messages, 'en');
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Deep Nested Structures
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
const structure = {
|
|
368
|
+
header: createI18n(['ja', 'en'] as const)
|
|
369
|
+
.add({ title: { ja: 'ヘッダー', en: 'Header' } }),
|
|
370
|
+
content: {
|
|
371
|
+
main: createI18n(['ja', 'en'] as const)
|
|
372
|
+
.add({ body: { ja: '本文', en: 'Body' } }),
|
|
373
|
+
sidebar: createI18n(['ja', 'en'] as const)
|
|
374
|
+
.add({ widget: { ja: 'ウィジェット', en: 'Widget' } }),
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const localized = bindLocale(structure, 'en');
|
|
379
|
+
console.log(localized.header.title.render()); // "Header"
|
|
380
|
+
console.log(localized.content.main.body.render()); // "Body"
|
|
381
|
+
console.log(localized.content.sidebar.widget.render()); // "Widget"
|
|
193
382
|
```
|
|
194
383
|
|
|
195
|
-
|
|
384
|
+
|
|
385
|
+
## Repository
|
|
386
|
+
|
|
387
|
+
https://github.com/MOhhh-ok/canopy-i18n
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ChainBuilder } from "./chainBuilder";
|
|
2
|
+
import { I18nMessage } from "./message";
|
|
3
|
+
export declare function isChainBuilder(x: unknown): x is ChainBuilder<any, any>;
|
|
4
|
+
type DeepUnwrapChainBuilders<T> = T extends I18nMessage<any, any> ? T : T extends ChainBuilder<any, infer Messages> ? Messages : T extends readonly any[] ? {
|
|
5
|
+
[K in keyof T]: DeepUnwrapChainBuilders<T[K]>;
|
|
6
|
+
} : T extends object ? {
|
|
7
|
+
[K in keyof T]: DeepUnwrapChainBuilders<T[K]>;
|
|
8
|
+
} : T;
|
|
9
|
+
export declare function bindLocale<T extends object>(obj: T, locale: string): DeepUnwrapChainBuilders<T>;
|
|
10
|
+
export {};
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.isChainBuilder = isChainBuilder;
|
|
4
|
+
exports.bindLocale = bindLocale;
|
|
5
|
+
const chainBuilder_1 = require("./chainBuilder");
|
|
4
6
|
const message_1 = require("./message");
|
|
5
|
-
function
|
|
7
|
+
function isChainBuilder(x) {
|
|
8
|
+
return x instanceof chainBuilder_1.ChainBuilder;
|
|
9
|
+
}
|
|
10
|
+
function bindLocale(obj, locale) {
|
|
6
11
|
function visit(v) {
|
|
12
|
+
if (isChainBuilder(v)) {
|
|
13
|
+
return v.build(locale);
|
|
14
|
+
}
|
|
7
15
|
if ((0, message_1.isI18nMessage)(v)) {
|
|
8
16
|
v.setLocale(locale);
|
|
9
17
|
return v;
|
|
@@ -11,7 +19,7 @@ function applyLocale(obj, locale) {
|
|
|
11
19
|
if (Array.isArray(v)) {
|
|
12
20
|
return v.map(visit);
|
|
13
21
|
}
|
|
14
|
-
if (v && typeof v ===
|
|
22
|
+
if (v && typeof v === "object") {
|
|
15
23
|
const out = {};
|
|
16
24
|
for (const k of Object.keys(v)) {
|
|
17
25
|
out[k] = visit(v[k]);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { I18nMessage, LocalizedMessage } from "./message";
|
|
2
|
+
export declare class ChainBuilder<const Ls extends readonly string[], Messages extends Record<string, I18nMessage<Ls, any>> = {}> {
|
|
3
|
+
private readonly locales;
|
|
4
|
+
private messages;
|
|
5
|
+
constructor(locales: Ls, messages?: Messages);
|
|
6
|
+
/**
|
|
7
|
+
* 文字列指定版: 複数のメッセージを一度に追加
|
|
8
|
+
*/
|
|
9
|
+
add<Entries extends Record<string, Record<Ls[number], string>>>(entries: {
|
|
10
|
+
[K in keyof Entries]: K extends keyof Messages ? never : Entries[K];
|
|
11
|
+
}): ChainBuilder<Ls, Messages & {
|
|
12
|
+
[K in keyof Entries]: LocalizedMessage<Ls, void>;
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* 関数指定版: 複数のテンプレート関数を一度に追加(型は統一)
|
|
16
|
+
*/
|
|
17
|
+
addTemplates<C>(): <Entries extends Record<string, Record<Ls[number], (ctx: C) => string>>>(entries: {
|
|
18
|
+
[K in keyof Entries]: K extends keyof Messages ? never : Entries[K];
|
|
19
|
+
}) => ChainBuilder<Ls, Messages & {
|
|
20
|
+
[K in keyof Entries]: LocalizedMessage<Ls, C>;
|
|
21
|
+
}>;
|
|
22
|
+
private deepCloneWithLocale;
|
|
23
|
+
build(locale?: Ls[number]): Messages;
|
|
24
|
+
/**
|
|
25
|
+
* 現在のChainBuilderの状態をコピーした新しいインスタンスを返す
|
|
26
|
+
*/
|
|
27
|
+
clone(): ChainBuilder<Ls, Messages>;
|
|
28
|
+
}
|
|
29
|
+
export declare function createI18n<const Ls extends readonly string[]>(locales: Ls): ChainBuilder<Ls, {}>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ChainBuilder = void 0;
|
|
4
|
+
exports.createI18n = createI18n;
|
|
5
|
+
const message_1 = require("./message");
|
|
6
|
+
class ChainBuilder {
|
|
7
|
+
locales;
|
|
8
|
+
messages;
|
|
9
|
+
constructor(locales, messages) {
|
|
10
|
+
this.locales = locales;
|
|
11
|
+
this.messages = (messages ?? {});
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 文字列指定版: 複数のメッセージを一度に追加
|
|
15
|
+
*/
|
|
16
|
+
add(entries) {
|
|
17
|
+
const newMessages = { ...this.messages };
|
|
18
|
+
for (const [key, data] of Object.entries(entries)) {
|
|
19
|
+
const msg = new message_1.I18nMessage(this.locales, this.locales[0]).setData(data);
|
|
20
|
+
newMessages[key] = msg;
|
|
21
|
+
}
|
|
22
|
+
return new ChainBuilder(this.locales, newMessages);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 関数指定版: 複数のテンプレート関数を一度に追加(型は統一)
|
|
26
|
+
*/
|
|
27
|
+
addTemplates() {
|
|
28
|
+
return (entries) => {
|
|
29
|
+
const newMessages = { ...this.messages };
|
|
30
|
+
for (const [key, data] of Object.entries(entries)) {
|
|
31
|
+
const msg = new message_1.I18nMessage(this.locales, this.locales[0]).setData(data);
|
|
32
|
+
newMessages[key] = msg;
|
|
33
|
+
}
|
|
34
|
+
return new ChainBuilder(this.locales, newMessages);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
deepCloneWithLocale(obj, locale) {
|
|
38
|
+
if ((0, message_1.isI18nMessage)(obj)) {
|
|
39
|
+
const cloned = Object.create(Object.getPrototypeOf(obj));
|
|
40
|
+
Object.assign(cloned, obj);
|
|
41
|
+
cloned.setLocale(locale);
|
|
42
|
+
return cloned;
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(obj)) {
|
|
45
|
+
return obj.map(v => this.deepCloneWithLocale(v, locale));
|
|
46
|
+
}
|
|
47
|
+
if (obj && typeof obj === "object") {
|
|
48
|
+
const out = {};
|
|
49
|
+
for (const k of Object.keys(obj)) {
|
|
50
|
+
out[k] = this.deepCloneWithLocale(obj[k], locale);
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
return obj;
|
|
55
|
+
}
|
|
56
|
+
build(locale) {
|
|
57
|
+
if (locale !== undefined) {
|
|
58
|
+
return this.deepCloneWithLocale(this.messages, locale);
|
|
59
|
+
}
|
|
60
|
+
return this.messages;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 現在のChainBuilderの状態をコピーした新しいインスタンスを返す
|
|
64
|
+
*/
|
|
65
|
+
clone() {
|
|
66
|
+
const clonedMessages = {};
|
|
67
|
+
for (const [key, msg] of Object.entries(this.messages)) {
|
|
68
|
+
if ((0, message_1.isI18nMessage)(msg)) {
|
|
69
|
+
// I18nMessageをクローン
|
|
70
|
+
const cloned = Object.create(Object.getPrototypeOf(msg));
|
|
71
|
+
Object.assign(cloned, msg);
|
|
72
|
+
clonedMessages[key] = cloned;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
clonedMessages[key] = msg;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return new ChainBuilder(this.locales, clonedMessages);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.ChainBuilder = ChainBuilder;
|
|
82
|
+
function createI18n(locales) {
|
|
83
|
+
return new ChainBuilder(locales);
|
|
84
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { createI18n } from
|
|
3
|
-
export { I18nMessage, isI18nMessage } from
|
|
4
|
-
export type { LocalizedMessage } from
|
|
5
|
-
export type { Template } from
|
|
1
|
+
export { bindLocale, isChainBuilder } from "./bindLocale";
|
|
2
|
+
export { ChainBuilder, createI18n } from "./chainBuilder";
|
|
3
|
+
export { I18nMessage, isI18nMessage } from "./message";
|
|
4
|
+
export type { LocalizedMessage } from "./message";
|
|
5
|
+
export type { Template } from "./types";
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isI18nMessage = exports.I18nMessage = exports.createI18n = exports.
|
|
4
|
-
var
|
|
5
|
-
Object.defineProperty(exports, "
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
exports.isI18nMessage = exports.I18nMessage = exports.createI18n = exports.ChainBuilder = exports.isChainBuilder = exports.bindLocale = void 0;
|
|
4
|
+
var bindLocale_1 = require("./bindLocale");
|
|
5
|
+
Object.defineProperty(exports, "bindLocale", { enumerable: true, get: function () { return bindLocale_1.bindLocale; } });
|
|
6
|
+
Object.defineProperty(exports, "isChainBuilder", { enumerable: true, get: function () { return bindLocale_1.isChainBuilder; } });
|
|
7
|
+
var chainBuilder_1 = require("./chainBuilder");
|
|
8
|
+
Object.defineProperty(exports, "ChainBuilder", { enumerable: true, get: function () { return chainBuilder_1.ChainBuilder; } });
|
|
9
|
+
Object.defineProperty(exports, "createI18n", { enumerable: true, get: function () { return chainBuilder_1.createI18n; } });
|
|
8
10
|
var message_1 = require("./message");
|
|
9
11
|
Object.defineProperty(exports, "I18nMessage", { enumerable: true, get: function () { return message_1.I18nMessage; } });
|
|
10
12
|
Object.defineProperty(exports, "isI18nMessage", { enumerable: true, get: function () { return message_1.isI18nMessage; } });
|
package/dist/message.d.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { Template } from "./types";
|
|
2
|
+
export type LocalizedMessage<Ls extends readonly string[], C> = I18nMessage<Ls, C>;
|
|
3
|
+
export declare function isI18nMessage(x: unknown): x is I18nMessage<any, any>;
|
|
2
4
|
export declare class I18nMessage<Ls extends readonly string[], C> {
|
|
3
5
|
readonly locales: Ls;
|
|
4
6
|
private _locale;
|
|
5
|
-
private _fallbackLocale;
|
|
6
7
|
private _data;
|
|
7
|
-
constructor(locales: Ls,
|
|
8
|
+
constructor(locales: Ls, locale: Ls[number]);
|
|
8
9
|
get locale(): Ls[number];
|
|
9
|
-
get fallbackLocale(): Ls[number];
|
|
10
10
|
setLocale(locale: Ls[number]): this;
|
|
11
|
-
setFallbackLocale(locale: Ls[number]): this;
|
|
12
11
|
get data(): Record<Ls[number], Template<C>>;
|
|
13
12
|
setData(data: Record<Ls[number], Template<C>>): this;
|
|
14
13
|
render(this: I18nMessage<Ls, void>): string;
|
|
15
14
|
render(ctx: C): string;
|
|
16
15
|
}
|
|
17
|
-
export type LocalizedMessage<Ls extends readonly string[], C> = I18nMessage<Ls, C>;
|
|
18
|
-
export declare function isI18nMessage(x: unknown): x is I18nMessage<any, any>;
|
package/dist/message.js
CHANGED
|
@@ -2,33 +2,25 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.I18nMessage = void 0;
|
|
4
4
|
exports.isI18nMessage = isI18nMessage;
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const types_1 = require("./types");
|
|
6
|
+
function isI18nMessage(x) {
|
|
7
|
+
return x instanceof I18nMessage;
|
|
7
8
|
}
|
|
8
9
|
class I18nMessage {
|
|
9
10
|
locales;
|
|
10
11
|
_locale;
|
|
11
|
-
_fallbackLocale;
|
|
12
12
|
_data;
|
|
13
|
-
constructor(locales,
|
|
13
|
+
constructor(locales, locale) {
|
|
14
14
|
this.locales = locales;
|
|
15
|
-
this.
|
|
16
|
-
this._locale = fallbackLocale;
|
|
15
|
+
this._locale = locale;
|
|
17
16
|
}
|
|
18
17
|
get locale() {
|
|
19
18
|
return this._locale;
|
|
20
19
|
}
|
|
21
|
-
get fallbackLocale() {
|
|
22
|
-
return this._fallbackLocale;
|
|
23
|
-
}
|
|
24
20
|
setLocale(locale) {
|
|
25
21
|
this._locale = locale;
|
|
26
22
|
return this;
|
|
27
23
|
}
|
|
28
|
-
setFallbackLocale(locale) {
|
|
29
|
-
this._fallbackLocale = locale;
|
|
30
|
-
return this;
|
|
31
|
-
}
|
|
32
24
|
get data() {
|
|
33
25
|
return this._data;
|
|
34
26
|
}
|
|
@@ -37,11 +29,8 @@ class I18nMessage {
|
|
|
37
29
|
return this;
|
|
38
30
|
}
|
|
39
31
|
render(ctx) {
|
|
40
|
-
const v = this._data[this._locale]
|
|
41
|
-
return isTemplateFunction(v) ? v(ctx) : v;
|
|
32
|
+
const v = this._data[this._locale];
|
|
33
|
+
return (0, types_1.isTemplateFunction)(v) ? v(ctx) : v;
|
|
42
34
|
}
|
|
43
35
|
}
|
|
44
36
|
exports.I18nMessage = I18nMessage;
|
|
45
|
-
function isI18nMessage(x) {
|
|
46
|
-
return x instanceof I18nMessage;
|
|
47
|
-
}
|
package/dist/types.d.ts
CHANGED
package/dist/types.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canopy-i18n",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A tiny, type-safe i18n helper",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,12 +20,20 @@
|
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"dev": "tsc --watch",
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"prepublishOnly": "pnpm run build",
|
|
27
|
+
"type-check": "tsc -p . --noEmit",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest"
|
|
30
|
+
},
|
|
23
31
|
"devDependencies": {
|
|
24
|
-
"@tsconfig/node20": "^20.1.
|
|
25
|
-
"@types/node": "^24.10.
|
|
26
|
-
"release-it": "^19.
|
|
32
|
+
"@tsconfig/node20": "^20.1.8",
|
|
33
|
+
"@types/node": "^24.10.4",
|
|
34
|
+
"release-it": "^19.2.2",
|
|
27
35
|
"typescript": "^5.9.3",
|
|
28
|
-
"vitest": "^4.0.
|
|
36
|
+
"vitest": "^4.0.16"
|
|
29
37
|
},
|
|
30
38
|
"keywords": [
|
|
31
39
|
"i18n",
|
|
@@ -42,12 +50,5 @@
|
|
|
42
50
|
"repository": {
|
|
43
51
|
"type": "git",
|
|
44
52
|
"url": "https://github.com/MOhhh-ok/canopy-i18n"
|
|
45
|
-
},
|
|
46
|
-
"scripts": {
|
|
47
|
-
"dev": "tsc --watch",
|
|
48
|
-
"build": "tsc",
|
|
49
|
-
"type-check": "tsc -p . --noEmit",
|
|
50
|
-
"test": "vitest run",
|
|
51
|
-
"test:watch": "vitest"
|
|
52
53
|
}
|
|
53
|
-
}
|
|
54
|
+
}
|
package/dist/applyLocale.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function applyLocale<T extends object>(obj: T, locale: string): T;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function applyLocaleDeep<T extends object>(obj: T, locale: string): T;
|
package/dist/applyLocaleDeep.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.applyLocaleDeep = applyLocaleDeep;
|
|
4
|
-
const message_1 = require("./message");
|
|
5
|
-
function applyLocaleDeep(obj, locale) {
|
|
6
|
-
function visit(v) {
|
|
7
|
-
if ((0, message_1.isI18nMessage)(v)) {
|
|
8
|
-
v.setLocale(locale);
|
|
9
|
-
return v;
|
|
10
|
-
}
|
|
11
|
-
if (Array.isArray(v)) {
|
|
12
|
-
return v.map(visit);
|
|
13
|
-
}
|
|
14
|
-
if (v && typeof v === 'object') {
|
|
15
|
-
const out = {};
|
|
16
|
-
for (const k of Object.keys(v)) {
|
|
17
|
-
out[k] = visit(v[k]);
|
|
18
|
-
}
|
|
19
|
-
return out;
|
|
20
|
-
}
|
|
21
|
-
return v;
|
|
22
|
-
}
|
|
23
|
-
return visit(obj);
|
|
24
|
-
}
|
package/dist/builder.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { Template } from "./types";
|
|
2
|
-
import { LocalizedMessage } from "./message";
|
|
3
|
-
export declare function createMessageBuilder<const Ls extends readonly string[]>(locales: Ls, fallbackLocale: Ls[number]): {
|
|
4
|
-
<C>(data: Record<Ls[number], Template<C>>): LocalizedMessage<Ls, C>;
|
|
5
|
-
(data: Record<Ls[number], string>): LocalizedMessage<Ls, void>;
|
|
6
|
-
};
|
package/dist/builder.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createMessageBuilder = createMessageBuilder;
|
|
4
|
-
const message_1 = require("./message");
|
|
5
|
-
function createMessageBuilder(locales, fallbackLocale) {
|
|
6
|
-
function builder(data) {
|
|
7
|
-
return new message_1.I18nMessage(locales, fallbackLocale).setData(data);
|
|
8
|
-
}
|
|
9
|
-
return builder;
|
|
10
|
-
}
|
package/dist/createBuilder.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export type Template<C> = string | ((ctx: C) => string);
|
|
2
|
-
export declare class Li18nMessage<Ls extends readonly string[], C> {
|
|
3
|
-
readonly locales: Ls;
|
|
4
|
-
private _locale;
|
|
5
|
-
private _fallbackLocale;
|
|
6
|
-
readonly data: Record<Ls[number], Template<C>>;
|
|
7
|
-
constructor(locales: Ls, _locale: Ls[number], _fallbackLocale: Ls[number], data: Record<Ls[number], Template<C>>);
|
|
8
|
-
get locale(): Ls[number];
|
|
9
|
-
get fallbackLocale(): Ls[number];
|
|
10
|
-
setLocale(locale: Ls[number]): this;
|
|
11
|
-
setFallbackLocale(locale: Ls[number]): this;
|
|
12
|
-
render(this: Li18nMessage<Ls, void>): string;
|
|
13
|
-
render(ctx: C): string;
|
|
14
|
-
}
|
|
15
|
-
export type LocalizedMessage<Ls extends readonly string[], C> = Li18nMessage<Ls, C>;
|
|
16
|
-
export declare function createMessageBuilder<const Ls extends readonly string[]>(locales: Ls, locale: Ls[number], fallbackLocale: Ls[number]): {
|
|
17
|
-
<C>(data: Record<Ls[number], Template<C>>): LocalizedMessage<Ls, C>;
|
|
18
|
-
(data: Record<Ls[number], string>): LocalizedMessage<Ls, void>;
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Recursively apply locale for any Li18nMessage instances found in the given value.
|
|
22
|
-
* Always deep; does not change fallback locale (keeps builder defaults).
|
|
23
|
-
*
|
|
24
|
-
* Example:
|
|
25
|
-
* import * as trs from './testData';
|
|
26
|
-
* applyLocaleDeep(trs, 'en');
|
|
27
|
-
*/
|
|
28
|
-
export declare function applyLocaleDeep<T extends Record<string, unknown>>(obj: T, locale: string): T;
|
|
29
|
-
export declare function applyLocaleDeep<T extends unknown[]>(obj: T, locale: string): T;
|
package/dist/createBuilder.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Li18nMessage = void 0;
|
|
4
|
-
exports.createMessageBuilder = createMessageBuilder;
|
|
5
|
-
exports.applyLocaleDeep = applyLocaleDeep;
|
|
6
|
-
function isTemplateFunction(t) {
|
|
7
|
-
return typeof t === 'function';
|
|
8
|
-
}
|
|
9
|
-
class Li18nMessage {
|
|
10
|
-
locales;
|
|
11
|
-
_locale;
|
|
12
|
-
_fallbackLocale;
|
|
13
|
-
data;
|
|
14
|
-
constructor(locales, _locale, _fallbackLocale, data) {
|
|
15
|
-
this.locales = locales;
|
|
16
|
-
this._locale = _locale;
|
|
17
|
-
this._fallbackLocale = _fallbackLocale;
|
|
18
|
-
this.data = data;
|
|
19
|
-
}
|
|
20
|
-
get locale() {
|
|
21
|
-
return this._locale;
|
|
22
|
-
}
|
|
23
|
-
get fallbackLocale() {
|
|
24
|
-
return this._fallbackLocale;
|
|
25
|
-
}
|
|
26
|
-
setLocale(locale) {
|
|
27
|
-
this._locale = locale;
|
|
28
|
-
return this;
|
|
29
|
-
}
|
|
30
|
-
setFallbackLocale(locale) {
|
|
31
|
-
this._fallbackLocale = locale;
|
|
32
|
-
return this;
|
|
33
|
-
}
|
|
34
|
-
render(ctx) {
|
|
35
|
-
const v = this.data[this._locale] ?? this.data[this._fallbackLocale];
|
|
36
|
-
return isTemplateFunction(v) ? v(ctx) : v;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
exports.Li18nMessage = Li18nMessage;
|
|
40
|
-
function createMessageBuilder(locales, locale, fallbackLocale) {
|
|
41
|
-
function builder(data) {
|
|
42
|
-
return new Li18nMessage(locales, locale, fallbackLocale, data);
|
|
43
|
-
}
|
|
44
|
-
return builder;
|
|
45
|
-
}
|
|
46
|
-
function isLi18nMessage(x) {
|
|
47
|
-
return x instanceof Li18nMessage;
|
|
48
|
-
}
|
|
49
|
-
function applyLocaleDeep(obj, locale) {
|
|
50
|
-
function visit(v) {
|
|
51
|
-
if (isLi18nMessage(v)) {
|
|
52
|
-
v.setLocale(locale);
|
|
53
|
-
return v;
|
|
54
|
-
}
|
|
55
|
-
if (Array.isArray(v)) {
|
|
56
|
-
return v.map(visit);
|
|
57
|
-
}
|
|
58
|
-
// Traverse any non-null object (including module namespace objects),
|
|
59
|
-
// but avoid special handling already covered above.
|
|
60
|
-
if (v && typeof v === 'object') {
|
|
61
|
-
const out = {};
|
|
62
|
-
for (const k of Object.keys(v)) {
|
|
63
|
-
out[k] = visit(v[k]);
|
|
64
|
-
}
|
|
65
|
-
return out;
|
|
66
|
-
}
|
|
67
|
-
return v;
|
|
68
|
-
}
|
|
69
|
-
return visit(obj);
|
|
70
|
-
}
|
package/dist/createI18n.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { Template } from "./types";
|
|
2
|
-
import { LocalizedMessage } from "./message";
|
|
3
|
-
export declare function createI18n<const Ls extends readonly string[]>(locales: Ls, fallbackLocale: Ls[number]): {
|
|
4
|
-
<C>(data: Record<Ls[number], Template<C>>, fb?: Ls[number]): LocalizedMessage<Ls, C>;
|
|
5
|
-
(data: Record<Ls[number], string>, fb?: Ls[number]): LocalizedMessage<Ls, void>;
|
|
6
|
-
};
|
package/dist/createI18n.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createI18n = createI18n;
|
|
4
|
-
const message_1 = require("./message");
|
|
5
|
-
function createI18n(locales, fallbackLocale) {
|
|
6
|
-
function builder(data, fb) {
|
|
7
|
-
return new message_1.I18nMessage(locales, fb ?? fallbackLocale).setData(data);
|
|
8
|
-
}
|
|
9
|
-
return builder;
|
|
10
|
-
}
|
package/dist/test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/test.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const index_1 = require("./index");
|
|
4
|
-
const trs = require("./testData");
|
|
5
|
-
for (const locale of ['ja', 'en']) {
|
|
6
|
-
const { msg, title, nested } = (0, index_1.applyLocaleDeep)(trs, locale);
|
|
7
|
-
console.log(msg.render({ name: '田中', age: 20 }));
|
|
8
|
-
console.log(title.render());
|
|
9
|
-
console.log(nested.hello.render());
|
|
10
|
-
const hs = (0, index_1.applyLocaleDeep)(trs.hasSentMsg, locale);
|
|
11
|
-
console.log(hs.render({ email: 'test@example.com' }));
|
|
12
|
-
}
|
package/dist/testData.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export declare const title: import("./message").LocalizedMessage<readonly ["ja", "en"], unknown>;
|
|
2
|
-
export declare const msg: import("./message").LocalizedMessage<readonly ["ja", "en"], {
|
|
3
|
-
name: string;
|
|
4
|
-
age: number;
|
|
5
|
-
}>;
|
|
6
|
-
export declare const nested: {
|
|
7
|
-
hello: import("./message").LocalizedMessage<readonly ["ja", "en"], unknown>;
|
|
8
|
-
world: import("./message").LocalizedMessage<readonly ["ja", "en"], unknown>;
|
|
9
|
-
};
|
|
10
|
-
export declare const hasSentMsg: import("./message").LocalizedMessage<readonly ["ja", "en"], {
|
|
11
|
-
email: string;
|
|
12
|
-
}>;
|
package/dist/testData.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.hasSentMsg = exports.nested = exports.msg = exports.title = void 0;
|
|
4
|
-
const index_1 = require("./index");
|
|
5
|
-
const builder = (0, index_1.createI18n)(['ja', 'en'], 'ja');
|
|
6
|
-
exports.title = builder({
|
|
7
|
-
ja: 'タイトルテスト',
|
|
8
|
-
en: 'Title Test'
|
|
9
|
-
});
|
|
10
|
-
exports.msg = builder({
|
|
11
|
-
ja: c => `こんにちは、${c.name}さん、あなたは${c.age}歳です。来年は${c.age + 1}歳です。`,
|
|
12
|
-
en: c => `Hello, ${c.name}. You are ${c.age} years old. Next year you will be ${c.age + 1} years old.`
|
|
13
|
-
});
|
|
14
|
-
exports.nested = {
|
|
15
|
-
hello: builder({
|
|
16
|
-
ja: 'こんにちは',
|
|
17
|
-
en: 'Hello'
|
|
18
|
-
}),
|
|
19
|
-
world: builder({
|
|
20
|
-
ja: '世界',
|
|
21
|
-
en: 'World'
|
|
22
|
-
})
|
|
23
|
-
};
|
|
24
|
-
exports.hasSentMsg = builder({
|
|
25
|
-
ja: o => `${o.email}にメールを送信しました。確認の上、処理を進めて下さい。`,
|
|
26
|
-
en: o => `Email sent to ${o.email}. Please check and proceed with the process.`
|
|
27
|
-
});
|