canopy-i18n 0.4.2 → 0.5.1
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 +33 -196
- package/dist/bindLocale.d.ts +4 -2
- package/dist/bindLocale.js +4 -2
- package/dist/chainBuilder.d.ts +5 -7
- package/dist/chainBuilder.js +8 -17
- package/dist/message.d.ts +7 -3
- package/dist/message.js +5 -0
- package/dist/testtest.d.ts +1 -0
- package/dist/testtest.js +14 -0
- package/package.json +8 -7
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Masaaki Ota
|
|
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
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
A tiny, type-safe i18n library for building localized messages with builder pattern and applying locales across nested data structures.
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
|
|
5
8
|
## Features
|
|
6
9
|
- **AI-friendly**: Full type safety and single-file colocation give AI assistants complete context for accurate code generation.
|
|
7
10
|
- **Type-safe**: Compile-time safety for locale keys with full TypeScript IntelliSense support.
|
|
@@ -36,7 +39,7 @@ const messages = createI18n(['en', 'ja'] as const).add({
|
|
|
36
39
|
farewell: { en: 'Goodbye', ja: 'さようなら' }
|
|
37
40
|
}).build('en');
|
|
38
41
|
|
|
39
|
-
console.log(messages.greeting); // Fully type-safe, autocomplete works
|
|
42
|
+
console.log(messages.greeting()); // Fully type-safe, autocomplete works
|
|
40
43
|
```
|
|
41
44
|
|
|
42
45
|
**With template functions:**
|
|
@@ -64,8 +67,8 @@ const menu = createI18n(['en', 'ja'] as const)
|
|
|
64
67
|
}
|
|
65
68
|
}).build('ja');
|
|
66
69
|
|
|
67
|
-
console.log(menu.home.label); // "ホーム"
|
|
68
|
-
console.log(menu.home.url); // "/ja"
|
|
70
|
+
console.log(menu.home().label); // "ホーム"
|
|
71
|
+
console.log(menu.home().url); // "/ja"
|
|
69
72
|
```
|
|
70
73
|
|
|
71
74
|
**Benefits:**
|
|
@@ -118,15 +121,15 @@ const builder = baseBuilder
|
|
|
118
121
|
const enMessages = builder.build('en');
|
|
119
122
|
const jaMessages = builder.build('ja');
|
|
120
123
|
|
|
121
|
-
// 4)
|
|
122
|
-
console.log(enMessages.title
|
|
123
|
-
console.log(enMessages.greeting
|
|
124
|
-
console.log(enMessages.welcome
|
|
124
|
+
// 4) Use messages (English)
|
|
125
|
+
console.log(enMessages.title()); // "Title Test"
|
|
126
|
+
console.log(enMessages.greeting()); // "Hello"
|
|
127
|
+
console.log(enMessages.welcome({ name: 'Tanaka', age: 20 })); // "Hello, Tanaka. You are 20 years old."
|
|
125
128
|
|
|
126
|
-
// 5)
|
|
127
|
-
console.log(jaMessages.title
|
|
128
|
-
console.log(jaMessages.greeting
|
|
129
|
-
console.log(jaMessages.welcome
|
|
129
|
+
// 5) Use messages (Japanese)
|
|
130
|
+
console.log(jaMessages.title()); // "タイトルテスト"
|
|
131
|
+
console.log(jaMessages.greeting()); // "こんにちは"
|
|
132
|
+
console.log(jaMessages.welcome({ name: 'Tanaka', age: 20 })); // "こんにちは、Tanakaさん。あなたは20歳です。"
|
|
130
133
|
```
|
|
131
134
|
|
|
132
135
|
## API
|
|
@@ -163,8 +166,8 @@ const builder = createI18n(['ja', 'en'] as const)
|
|
|
163
166
|
const messages = createI18n(['ja', 'en'] as const)
|
|
164
167
|
.add<JSX.Element>({
|
|
165
168
|
badge: {
|
|
166
|
-
ja: <span style={{ color: '
|
|
167
|
-
en: <span style={{ color: '
|
|
169
|
+
ja: <span style={{ background: '#ff4444', color: 'white', padding: '2px 6px', borderRadius: '2px' }}>🔴 新着</span>,
|
|
170
|
+
en: <span style={{ background: '#4caf50', color: 'white', padding: '4px 12px', borderRadius: '16px' }}>✨ NEW</span>,
|
|
168
171
|
},
|
|
169
172
|
});
|
|
170
173
|
|
|
@@ -172,13 +175,14 @@ const messages = createI18n(['ja', 'en'] as const)
|
|
|
172
175
|
type MenuItem = {
|
|
173
176
|
label: string;
|
|
174
177
|
url: string;
|
|
178
|
+
icon: string;
|
|
175
179
|
};
|
|
176
180
|
|
|
177
181
|
const menu = createI18n(['ja', 'en'] as const)
|
|
178
182
|
.add<MenuItem>({
|
|
179
183
|
home: {
|
|
180
|
-
ja: { label: 'ホーム', url: '/' },
|
|
181
|
-
en: { label: 'Home', url: '/' },
|
|
184
|
+
ja: { label: 'ホーム', url: '/ja', icon: '🏠' },
|
|
185
|
+
en: { label: 'Home', url: '/en', icon: '🏡' },
|
|
182
186
|
},
|
|
183
187
|
});
|
|
184
188
|
```
|
|
@@ -226,55 +230,6 @@ const japaneseMessages = builder.build('ja');
|
|
|
226
230
|
|
|
227
231
|
**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.
|
|
228
232
|
|
|
229
|
-
#### `.clone()`
|
|
230
|
-
Creates an independent copy of the current builder with all its messages.
|
|
231
|
-
|
|
232
|
-
- Returns: `ChainBuilder<Locales, Messages>` — A new builder instance with cloned messages
|
|
233
|
-
|
|
234
|
-
```ts
|
|
235
|
-
const builder1 = createI18n(['ja', 'en'] as const)
|
|
236
|
-
.add({
|
|
237
|
-
title: { ja: 'タイトル', en: 'Title' },
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
const builder2 = builder1.clone().add({
|
|
241
|
-
greeting: { ja: 'こんにちは', en: 'Hello' },
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
const messages1 = builder1.build('ja');
|
|
245
|
-
const messages2 = builder2.build('ja');
|
|
246
|
-
|
|
247
|
-
console.log(messages1.title.render()); // "タイトル"
|
|
248
|
-
console.log(messages1.greeting); // undefined
|
|
249
|
-
|
|
250
|
-
console.log(messages2.title.render()); // "タイトル"
|
|
251
|
-
console.log(messages2.greeting.render()); // "こんにちは"
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
**Note**: `clone()` creates a deep copy of all messages, allowing you to branch off from a base builder and add different messages independently.
|
|
255
|
-
|
|
256
|
-
### `I18nMessage<Locales, Context, ReturnType = string>`
|
|
257
|
-
Represents a single localized message.
|
|
258
|
-
|
|
259
|
-
#### Properties
|
|
260
|
-
- `locales: Locales` — Readonly array of allowed locales
|
|
261
|
-
- `locale: Locale` — Current active locale (getter)
|
|
262
|
-
- `data: Record<Locale, Template<Context, ReturnType>>` — Message data for all locales (getter)
|
|
263
|
-
|
|
264
|
-
#### Methods
|
|
265
|
-
- `setLocale(locale: Locale): this` — Sets the active locale
|
|
266
|
-
- `setData(data: Record<Locale, Template<Context, ReturnType>>): this` — Sets the message data
|
|
267
|
-
- `render(ctx?: Context): ReturnType` — Renders the message for the active locale and returns the specified type
|
|
268
|
-
|
|
269
|
-
```ts
|
|
270
|
-
const message = messages.title;
|
|
271
|
-
console.log(message.locale); // Current locale
|
|
272
|
-
console.log(message.render()); // Rendered string
|
|
273
|
-
|
|
274
|
-
message.setLocale('ja');
|
|
275
|
-
console.log(message.render()); // Japanese version
|
|
276
|
-
```
|
|
277
|
-
|
|
278
233
|
### `bindLocale(obj, locale)`
|
|
279
234
|
Recursively traverses objects/arrays and sets the given locale on all `I18nMessage` instances and builds all `ChainBuilder` instances encountered.
|
|
280
235
|
|
|
@@ -291,8 +246,8 @@ const data = {
|
|
|
291
246
|
};
|
|
292
247
|
|
|
293
248
|
const localized = bindLocale(data, 'en');
|
|
294
|
-
console.log(localized.common.title
|
|
295
|
-
console.log(localized.nested.special.msg
|
|
249
|
+
console.log(localized.common.title()); // English version
|
|
250
|
+
console.log(localized.nested.special.msg()); // English version
|
|
296
251
|
```
|
|
297
252
|
|
|
298
253
|
**Note**: `bindLocale` works with both `ChainBuilder` instances (automatically building them with the specified locale) and already-built message objects (updating their locale).
|
|
@@ -315,91 +270,6 @@ export type { Template, LocalizedMessage } from 'canopy-i18n';
|
|
|
315
270
|
|
|
316
271
|
## Usage Patterns
|
|
317
272
|
|
|
318
|
-
### React Components as Messages
|
|
319
|
-
|
|
320
|
-
```ts
|
|
321
|
-
import { createI18n } from 'canopy-i18n';
|
|
322
|
-
|
|
323
|
-
// Static React components
|
|
324
|
-
const messages = createI18n(['ja', 'en'] as const)
|
|
325
|
-
.add<JSX.Element>({
|
|
326
|
-
badge: {
|
|
327
|
-
ja: <span style={{ background: '#4caf50', color: 'white', padding: '4px 8px', borderRadius: '4px' }}>新着</span>,
|
|
328
|
-
en: <span style={{ background: '#4caf50', color: 'white', padding: '4px 8px', borderRadius: '4px' }}>NEW</span>,
|
|
329
|
-
},
|
|
330
|
-
alert: {
|
|
331
|
-
ja: <div style={{ background: '#fff3cd', padding: '12px', borderRadius: '4px' }}>⚠️ これは警告です</div>,
|
|
332
|
-
en: <div style={{ background: '#fff3cd', padding: '12px', borderRadius: '4px' }}>⚠️ This is a warning</div>,
|
|
333
|
-
},
|
|
334
|
-
})
|
|
335
|
-
.build('en');
|
|
336
|
-
|
|
337
|
-
// Render in React
|
|
338
|
-
function MyComponent() {
|
|
339
|
-
return (
|
|
340
|
-
<div>
|
|
341
|
-
{messages.badge.render()}
|
|
342
|
-
{messages.alert.render()}
|
|
343
|
-
</div>
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Dynamic React components with context
|
|
348
|
-
type ButtonContext = {
|
|
349
|
-
onClick: () => void;
|
|
350
|
-
text: string;
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
const dynamicMessages = createI18n(['ja', 'en'] as const)
|
|
354
|
-
.addTemplates<ButtonContext, JSX.Element>()({
|
|
355
|
-
button: {
|
|
356
|
-
ja: (ctx) => (
|
|
357
|
-
<button onClick={ctx.onClick} style={{ background: '#2196f3', color: 'white', padding: '8px 16px' }}>
|
|
358
|
-
{ctx.text}
|
|
359
|
-
</button>
|
|
360
|
-
),
|
|
361
|
-
en: (ctx) => (
|
|
362
|
-
<button onClick={ctx.onClick} style={{ background: '#2196f3', color: 'white', padding: '8px 16px' }}>
|
|
363
|
-
{ctx.text}
|
|
364
|
-
</button>
|
|
365
|
-
),
|
|
366
|
-
},
|
|
367
|
-
})
|
|
368
|
-
.build('en');
|
|
369
|
-
|
|
370
|
-
// Use with context
|
|
371
|
-
function AnotherComponent() {
|
|
372
|
-
return <div>{dynamicMessages.button.render({ onClick: () => alert('Clicked!'), text: 'Click me' })}</div>;
|
|
373
|
-
}
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
### Custom Object Types
|
|
377
|
-
|
|
378
|
-
```ts
|
|
379
|
-
type MenuItem = {
|
|
380
|
-
label: string;
|
|
381
|
-
url: string;
|
|
382
|
-
icon: string;
|
|
383
|
-
};
|
|
384
|
-
|
|
385
|
-
const menuMessages = createI18n(['ja', 'en'] as const)
|
|
386
|
-
.add<MenuItem>({
|
|
387
|
-
home: {
|
|
388
|
-
ja: { label: 'ホーム', url: '/', icon: '🏠' },
|
|
389
|
-
en: { label: 'Home', url: '/', icon: '🏠' },
|
|
390
|
-
},
|
|
391
|
-
settings: {
|
|
392
|
-
ja: { label: '設定', url: '/settings', icon: '⚙️' },
|
|
393
|
-
en: { label: 'Settings', url: '/settings', icon: '⚙️' },
|
|
394
|
-
},
|
|
395
|
-
})
|
|
396
|
-
.build('en');
|
|
397
|
-
|
|
398
|
-
const homeMenu = menuMessages.home.render();
|
|
399
|
-
console.log(homeMenu.label); // "Home"
|
|
400
|
-
console.log(homeMenu.url); // "/"
|
|
401
|
-
console.log(homeMenu.icon); // "🏠"
|
|
402
|
-
```
|
|
403
273
|
|
|
404
274
|
### Basic String Messages
|
|
405
275
|
|
|
@@ -412,8 +282,8 @@ const messages = createI18n(['ja', 'en'] as const)
|
|
|
412
282
|
})
|
|
413
283
|
.build('en');
|
|
414
284
|
|
|
415
|
-
console.log(messages.title
|
|
416
|
-
console.log(messages.greeting
|
|
285
|
+
console.log(messages.title()); // "Title"
|
|
286
|
+
console.log(messages.greeting()); // "Hello"
|
|
417
287
|
```
|
|
418
288
|
|
|
419
289
|
### Template Functions with Context
|
|
@@ -428,7 +298,7 @@ const messages = createI18n(['ja', 'en'] as const)
|
|
|
428
298
|
})
|
|
429
299
|
.build('en');
|
|
430
300
|
|
|
431
|
-
console.log(messages.profile
|
|
301
|
+
console.log(messages.profile({ name: 'Taro', age: 25 }));
|
|
432
302
|
// "Name: Taro, Age: 25"
|
|
433
303
|
```
|
|
434
304
|
|
|
@@ -447,39 +317,11 @@ const messages = createI18n(['ja', 'en'] as const)
|
|
|
447
317
|
})
|
|
448
318
|
.build('ja');
|
|
449
319
|
|
|
450
|
-
console.log(messages.title
|
|
451
|
-
console.log(messages.items
|
|
320
|
+
console.log(messages.title()); // "タイトル"
|
|
321
|
+
console.log(messages.items({ count: 5 })); // "5個のアイテム"
|
|
452
322
|
```
|
|
453
323
|
|
|
454
|
-
### Using Clone for Shared Base Messages
|
|
455
|
-
|
|
456
|
-
```ts
|
|
457
|
-
// Create a base builder with common messages
|
|
458
|
-
const baseBuilder = createI18n(['ja', 'en'] as const)
|
|
459
|
-
.add({
|
|
460
|
-
common: { ja: '共通', en: 'Common' },
|
|
461
|
-
error: { ja: 'エラー', en: 'Error' },
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
// Clone and extend for admin pages
|
|
465
|
-
const adminMessages = baseBuilder.clone()
|
|
466
|
-
.add({
|
|
467
|
-
adminTitle: { ja: '管理画面', en: 'Admin Panel' },
|
|
468
|
-
})
|
|
469
|
-
.build('en');
|
|
470
324
|
|
|
471
|
-
// Clone and extend for user pages
|
|
472
|
-
const userMessages = baseBuilder.clone()
|
|
473
|
-
.add({
|
|
474
|
-
userTitle: { ja: 'ユーザー画面', en: 'User Panel' },
|
|
475
|
-
})
|
|
476
|
-
.build('en');
|
|
477
|
-
|
|
478
|
-
console.log(adminMessages.common.render()); // "Common"
|
|
479
|
-
console.log(adminMessages.adminTitle.render()); // "Admin Panel"
|
|
480
|
-
console.log(userMessages.common.render()); // "Common"
|
|
481
|
-
console.log(userMessages.userTitle.render()); // "User Panel"
|
|
482
|
-
```
|
|
483
325
|
|
|
484
326
|
### Namespace Pattern (Split Files)
|
|
485
327
|
|
|
@@ -516,8 +358,8 @@ import { bindLocale } from 'canopy-i18n';
|
|
|
516
358
|
import * as i18n from './i18n';
|
|
517
359
|
|
|
518
360
|
const messages = bindLocale(i18n, 'en');
|
|
519
|
-
console.log(messages.common.hello
|
|
520
|
-
console.log(messages.user.welcome
|
|
361
|
+
console.log(messages.common.hello()); // "Hello"
|
|
362
|
+
console.log(messages.user.welcome({ name: 'John' })); // "Welcome, John"
|
|
521
363
|
```
|
|
522
364
|
|
|
523
365
|
### Dynamic Locale Switching
|
|
@@ -532,13 +374,8 @@ const builder = createI18n(['ja', 'en'] as const)
|
|
|
532
374
|
const jaMessages = builder.build('ja');
|
|
533
375
|
const enMessages = builder.build('en');
|
|
534
376
|
|
|
535
|
-
console.log(jaMessages.title
|
|
536
|
-
console.log(enMessages.title
|
|
537
|
-
|
|
538
|
-
// Or use bindLocale to switch locale dynamically
|
|
539
|
-
const messages = builder.build();
|
|
540
|
-
const localizedJa = bindLocale(messages, 'ja');
|
|
541
|
-
const localizedEn = bindLocale(messages, 'en');
|
|
377
|
+
console.log(jaMessages.title()); // "タイトル"
|
|
378
|
+
console.log(enMessages.title()); // "Title"
|
|
542
379
|
```
|
|
543
380
|
|
|
544
381
|
### Deep Nested Structures
|
|
@@ -556,9 +393,9 @@ const structure = {
|
|
|
556
393
|
};
|
|
557
394
|
|
|
558
395
|
const localized = bindLocale(structure, 'en');
|
|
559
|
-
console.log(localized.header.title
|
|
560
|
-
console.log(localized.content.main.body
|
|
561
|
-
console.log(localized.content.sidebar.widget
|
|
396
|
+
console.log(localized.header.title()); // "Header"
|
|
397
|
+
console.log(localized.content.main.body()); // "Body"
|
|
398
|
+
console.log(localized.content.sidebar.widget()); // "Widget"
|
|
562
399
|
```
|
|
563
400
|
|
|
564
401
|
|
package/dist/bindLocale.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ChainBuilder } from "./chainBuilder.js";
|
|
2
|
-
import { I18nMessage } from "./message.js";
|
|
2
|
+
import { I18nMessage, type LocalizedMessage } from "./message.js";
|
|
3
3
|
export declare function isChainBuilder(x: unknown): x is ChainBuilder<any, any>;
|
|
4
|
-
type DeepUnwrapChainBuilders<T> = T extends I18nMessage<
|
|
4
|
+
type DeepUnwrapChainBuilders<T> = T extends I18nMessage<infer Ls, infer C, infer R> ? LocalizedMessage<Ls, C, R> : T extends ChainBuilder<infer Ls, infer Messages> ? {
|
|
5
|
+
[K in keyof Messages]: Messages[K] extends I18nMessage<Ls, infer C, infer R> ? LocalizedMessage<Ls, C, R> : never;
|
|
6
|
+
} : T extends readonly any[] ? {
|
|
5
7
|
[K in keyof T]: DeepUnwrapChainBuilders<T[K]>;
|
|
6
8
|
} : T extends object ? {
|
|
7
9
|
[K in keyof T]: DeepUnwrapChainBuilders<T[K]>;
|
package/dist/bindLocale.js
CHANGED
|
@@ -9,8 +9,10 @@ export function bindLocale(obj, locale) {
|
|
|
9
9
|
return v.build(locale);
|
|
10
10
|
}
|
|
11
11
|
if (isI18nMessage(v)) {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const cloned = Object.create(Object.getPrototypeOf(v));
|
|
13
|
+
Object.assign(cloned, v);
|
|
14
|
+
cloned.setLocale(locale);
|
|
15
|
+
return cloned.toFunction();
|
|
14
16
|
}
|
|
15
17
|
if (Array.isArray(v)) {
|
|
16
18
|
return v.map(visit);
|
package/dist/chainBuilder.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare class ChainBuilder<const Ls extends readonly string[], Messages e
|
|
|
11
11
|
add<R = string, Entries extends Record<string, Record<Ls[number], R>> = Record<string, Record<Ls[number], R>>>(entries: {
|
|
12
12
|
[K in keyof Entries]: K extends keyof Messages ? never : Entries[K];
|
|
13
13
|
}): ChainBuilder<Ls, Messages & {
|
|
14
|
-
[K in keyof Entries]:
|
|
14
|
+
[K in keyof Entries]: I18nMessage<Ls, void, R>;
|
|
15
15
|
}>;
|
|
16
16
|
/**
|
|
17
17
|
* 関数指定版: 複数のテンプレート関数を一度に追加(型は統一)
|
|
@@ -19,13 +19,11 @@ export declare class ChainBuilder<const Ls extends readonly string[], Messages e
|
|
|
19
19
|
addTemplates<C, R = string>(): <Entries extends Record<string, Record<Ls[number], (ctx: C) => R>>>(entries: {
|
|
20
20
|
[K in keyof Entries]: K extends keyof Messages ? never : Entries[K];
|
|
21
21
|
}) => ChainBuilder<Ls, Messages & {
|
|
22
|
-
[K in keyof Entries]:
|
|
22
|
+
[K in keyof Entries]: I18nMessage<Ls, C, R>;
|
|
23
23
|
}>;
|
|
24
24
|
private deepCloneWithLocale;
|
|
25
|
-
build
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*/
|
|
29
|
-
clone(): ChainBuilder<Ls, Messages>;
|
|
25
|
+
build<M = {
|
|
26
|
+
[K in keyof Messages]: Messages[K] extends I18nMessage<Ls, infer C, infer R> ? LocalizedMessage<Ls, C, R> : never;
|
|
27
|
+
}>(locale?: Ls[number]): M;
|
|
30
28
|
}
|
|
31
29
|
export declare function createI18n<const Ls extends readonly string[]>(locales: Ls): ChainBuilder<Ls, {}>;
|
package/dist/chainBuilder.js
CHANGED
|
@@ -51,28 +51,19 @@ export class ChainBuilder {
|
|
|
51
51
|
return obj;
|
|
52
52
|
}
|
|
53
53
|
build(locale) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 現在のChainBuilderの状態をコピーした新しいインスタンスを返す
|
|
61
|
-
*/
|
|
62
|
-
clone() {
|
|
63
|
-
const clonedMessages = {};
|
|
64
|
-
for (const [key, msg] of Object.entries(this.messages)) {
|
|
54
|
+
const clonedMessages = locale !== undefined
|
|
55
|
+
? this.deepCloneWithLocale(this.messages, locale)
|
|
56
|
+
: this.messages;
|
|
57
|
+
const result = {};
|
|
58
|
+
for (const [key, msg] of Object.entries(clonedMessages)) {
|
|
65
59
|
if (isI18nMessage(msg)) {
|
|
66
|
-
|
|
67
|
-
const cloned = Object.create(Object.getPrototypeOf(msg));
|
|
68
|
-
Object.assign(cloned, msg);
|
|
69
|
-
clonedMessages[key] = cloned;
|
|
60
|
+
result[key] = msg.toFunction();
|
|
70
61
|
}
|
|
71
62
|
else {
|
|
72
|
-
|
|
63
|
+
result[key] = msg;
|
|
73
64
|
}
|
|
74
65
|
}
|
|
75
|
-
return
|
|
66
|
+
return result;
|
|
76
67
|
}
|
|
77
68
|
}
|
|
78
69
|
export function createI18n(locales) {
|
package/dist/message.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { Template } from "./types.js";
|
|
2
|
-
export type LocalizedMessage<Ls extends readonly string[], C, R = string> =
|
|
2
|
+
export type LocalizedMessage<Ls extends readonly string[], C, R = string> = C extends void ? (() => R) & {
|
|
3
|
+
__brand: "I18nMessage";
|
|
4
|
+
} : ((ctx: C) => R) & {
|
|
5
|
+
__brand: "I18nTemplateMessage";
|
|
6
|
+
};
|
|
3
7
|
export declare function isI18nMessage(x: unknown): x is I18nMessage<any, any, any>;
|
|
4
8
|
export declare class I18nMessage<Ls extends readonly string[], C, R = string> {
|
|
5
9
|
readonly locales: Ls;
|
|
@@ -10,6 +14,6 @@ export declare class I18nMessage<Ls extends readonly string[], C, R = string> {
|
|
|
10
14
|
setLocale(locale: Ls[number]): this;
|
|
11
15
|
get data(): Record<Ls[number], Template<C, R>>;
|
|
12
16
|
setData(data: Record<Ls[number], Template<C, R>>): this;
|
|
13
|
-
render
|
|
14
|
-
|
|
17
|
+
private render;
|
|
18
|
+
toFunction(): LocalizedMessage<Ls, C, R>;
|
|
15
19
|
}
|
package/dist/message.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/testtest.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createI18n } from "./chainBuilder.js";
|
|
2
|
+
const a = createI18n(["ja", "en"]);
|
|
3
|
+
const b = a
|
|
4
|
+
.add({ abc: { ja: "abc", en: "abc" } })
|
|
5
|
+
.addTemplates()({
|
|
6
|
+
bb: { ja: ({ aa }) => `${aa}aa`, en: ({ aa }) => `${aa}bb` },
|
|
7
|
+
});
|
|
8
|
+
const c = b.build("en");
|
|
9
|
+
const e = { ...c.abc, toString: () => "aaa" };
|
|
10
|
+
console.log(c.bb({ aa: "name" }));
|
|
11
|
+
console.log(`${c.bb({ aa: "name" })}`);
|
|
12
|
+
console.log("aaa" + c.bb({ aa: "name" }));
|
|
13
|
+
console.log(`${c.bb({ aa: "name" })}`);
|
|
14
|
+
console.log(`${c.abc()}`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canopy-i18n",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "The Type-Safe i18n library that your IDE will Love",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -25,14 +25,15 @@
|
|
|
25
25
|
"prepublishOnly": "pnpm run build",
|
|
26
26
|
"type-check": "tsc -p . --noEmit",
|
|
27
27
|
"test": "vitest run",
|
|
28
|
-
"test:watch": "vitest"
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"release": "release-it"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@tsconfig/node20": "^20.1.8",
|
|
32
|
-
"@types/node": "^
|
|
33
|
-
"release-it": "^19.2.
|
|
33
|
+
"@types/node": "^25.0.10",
|
|
34
|
+
"release-it": "^19.2.4",
|
|
34
35
|
"typescript": "^5.9.3",
|
|
35
|
-
"vitest": "^4.0.
|
|
36
|
+
"vitest": "^4.0.18"
|
|
36
37
|
},
|
|
37
38
|
"keywords": [
|
|
38
39
|
"i18n",
|
|
@@ -48,6 +49,6 @@
|
|
|
48
49
|
"license": "MIT",
|
|
49
50
|
"repository": {
|
|
50
51
|
"type": "git",
|
|
51
|
-
"url": "https://github.com/
|
|
52
|
+
"url": "https://github.com/mohhh-ok/canopy-i18n"
|
|
52
53
|
}
|
|
53
54
|
}
|