canopy-i18n 0.1.1 → 0.3.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 CHANGED
@@ -1,13 +1,14 @@
1
- ## canopy-i18n
1
+ # canopy-i18n
2
2
 
3
- A tiny, type-safe i18n helper for building localized messages and applying a locale across nested data structures.
3
+ A tiny, type-safe i18n library for building localized messages with builder pattern and applying locales across nested data structures.
4
4
 
5
- ### Features
5
+ ## Features
6
6
  - **Type-safe locales**: Compile-time safety for allowed locale keys.
7
- - **Per-message fallback**: Each message knows its default and fallback locale.
8
- - **Template or string**: Use plain strings or `(ctx) => string` templates.
9
- - **Flexible templating**: Since templates are plain functions, you can freely use JavaScript template literals, conditionals, helpers, or any formatting library. This library does not provide a tagged template literal API.
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
+ - **Generic return types**: Return any type (string, React components, etc.) from your messages.
11
+ - **Deep locale application**: Switch locale across entire object/array trees, including nested builders.
11
12
 
12
13
  ## Installation
13
14
 
@@ -17,179 +18,485 @@ npm install canopy-i18n
17
18
  pnpm add canopy-i18n
18
19
  # or
19
20
  yarn add canopy-i18n
21
+ # or
22
+ bun add canopy-i18n
20
23
  ```
21
24
 
22
- ## Quick start
25
+ ## Quick Start
23
26
 
24
27
  ```ts
25
- import { createI18n, applyLocale } from 'canopy-i18n';
28
+ import { createI18n, bindLocale } from 'canopy-i18n';
29
+
30
+ // 1) Create a builder with allowed locales
31
+ const baseBuilder = createI18n(['ja', 'en'] as const);
32
+
33
+ // 2) Define messages using method chaining (store in a variable)
34
+ const builder = baseBuilder
35
+ .add({
36
+ title: {
37
+ ja: 'タイトルテスト',
38
+ en: 'Title Test',
39
+ },
40
+ greeting: {
41
+ ja: 'こんにちは',
42
+ en: 'Hello',
43
+ },
44
+ })
45
+ .addTemplates<{ name: string; age: number }>()({
46
+ welcome: {
47
+ ja: (ctx) => `こんにちは、${ctx.name}さん。あなたは${ctx.age}歳です。`,
48
+ en: (ctx) => `Hello, ${ctx.name}. You are ${ctx.age} years old.`,
49
+ },
50
+ });
51
+
52
+ // 3) Reuse the builder to create messages for different locales
53
+ const enMessages = builder.build('en');
54
+ const jaMessages = builder.build('ja');
55
+
56
+ // 4) Render messages (English)
57
+ console.log(enMessages.title.render()); // "Title Test"
58
+ console.log(enMessages.greeting.render()); // "Hello"
59
+ console.log(enMessages.welcome.render({ name: 'Tanaka', age: 20 })); // "Hello, Tanaka. You are 20 years old."
60
+
61
+ // 5) Render messages (Japanese)
62
+ console.log(jaMessages.title.render()); // "タイトルテスト"
63
+ console.log(jaMessages.greeting.render()); // "こんにちは"
64
+ console.log(jaMessages.welcome.render({ name: 'Tanaka', age: 20 })); // "こんにちは、Tanakaさん。あなたは20歳です。"
65
+ ```
26
66
 
27
- // 1) Declare allowed locales and fallback
28
- const defineMessage = createI18n(['ja', 'en'] as const, 'ja');
67
+ ## API
29
68
 
30
- // 2) Define messages
31
- const title = defineMessage({
32
- ja: 'タイトルテスト',
33
- en: 'Title Test',
34
- });
69
+ ### `createI18n(locales)`
70
+ Creates a `ChainBuilder` instance to build localized messages.
35
71
 
36
- const msg = defineMessage<{ name: string; age: number }>({
37
- ja: c => `こんにちは、${c.name}さん。あなたは${c.age}歳です。`,
38
- en: c => `Hello, ${c.name}. You are ${c.age} years old.`,
39
- });
72
+ - **locales**: `readonly string[]` Allowed locale keys (e.g. `['ja', 'en'] as const`).
73
+ - Returns: `ChainBuilder<Locales, {}>` — A builder instance to chain message definitions.
40
74
 
41
- // 3) Compose nested data structures
42
- const data = {
43
- title,
44
- nested: {
45
- hello: defineMessage({ ja: 'こんにちは', en: 'Hello' }),
46
- },
75
+ ```ts
76
+ const builder = createI18n(['ja', 'en', 'fr'] as const);
77
+ ```
78
+
79
+ ### `ChainBuilder`
80
+ A builder class for creating multiple localized messages with method chaining.
81
+
82
+ #### `.add<ReturnType = string>(entries)`
83
+ Adds multiple messages at once. By default, returns `string`, but you can specify a custom return type.
84
+
85
+ - **ReturnType**: (optional) Type parameter for the return value (defaults to `string`)
86
+ - **entries**: `Record<string, Record<Locale, ReturnType>>`
87
+ - Returns: `ChainBuilder` with added messages
88
+
89
+ ```ts
90
+ // String messages (default)
91
+ const builder = createI18n(['ja', 'en'] as const)
92
+ .add({
93
+ title: { ja: 'タイトル', en: 'Title' },
94
+ greeting: { ja: 'こんにちは', en: 'Hello' },
95
+ });
96
+
97
+ // Custom return type (e.g., React components)
98
+ const messages = createI18n(['ja', 'en'] as const)
99
+ .add<JSX.Element>({
100
+ badge: {
101
+ ja: <span style={{ color: 'red' }}>新着</span>,
102
+ en: <span style={{ color: 'red' }}>NEW</span>,
103
+ },
104
+ });
105
+
106
+ // Custom return type (objects)
107
+ type MenuItem = {
108
+ label: string;
109
+ url: string;
47
110
  };
48
111
 
49
- // 4) Apply locale across the tree
50
- const localized = applyLocale(data, 'en');
112
+ const menu = createI18n(['ja', 'en'] as const)
113
+ .add<MenuItem>({
114
+ home: {
115
+ ja: { label: 'ホーム', url: '/' },
116
+ en: { label: 'Home', url: '/' },
117
+ },
118
+ });
119
+ ```
120
+
121
+ #### `.addTemplates<Context, ReturnType = string>()(entries)`
122
+ Adds multiple template function messages at once with a unified context type and custom return type.
123
+
124
+ Note: This uses a curried API for better type inference. Call `addTemplates<Context, ReturnType>()` first, then call the returned function with entries.
125
+
126
+ - **Context**: Type parameter for the template function context
127
+ - **ReturnType**: (optional) Type parameter for the return value (defaults to `string`)
128
+ - **entries**: `Record<string, Record<Locale, (ctx: Context) => ReturnType>>`
129
+ - Returns: `ChainBuilder` with added template messages
130
+
131
+ ```ts
132
+ const builder = createI18n(['ja', 'en'] as const)
133
+ .addTemplates<{ name: string; age: number }>()({
134
+ greet: {
135
+ ja: (ctx) => `こんにちは、${ctx.name}さん。${ctx.age}歳ですね。`,
136
+ en: (ctx) => `Hello, ${ctx.name}. You are ${ctx.age}.`,
137
+ },
138
+ farewell: {
139
+ ja: (ctx) => `さようなら、${ctx.name}さん。`,
140
+ en: (ctx) => `Goodbye, ${ctx.name}.`,
141
+ },
142
+ });
143
+ ```
144
+
145
+
146
+
147
+ #### `.build(locale?)`
148
+ Builds the final messages object.
149
+
150
+ - **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.
151
+ - Returns: `Messages` — An object containing all defined messages
51
152
 
52
- console.log(localized.title.render()); // "Title Test"
53
- console.log(localized.nested.hello.render()); // "Hello"
54
- console.log(msg.setLocale('en').render({ name: 'Tanaka', age: 20 }));
153
+ ```ts
154
+ // Build with default locale (first in array)
155
+ const defaultMessages = builder.build();
156
+
157
+ // Build with specific locale
158
+ const englishMessages = builder.build('en');
159
+ const japaneseMessages = builder.build('ja');
55
160
  ```
56
161
 
57
- ## API
162
+ **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.
58
163
 
59
- ### createI18n(locales, fallbackLocale)
60
- Returns a `defineMessage` function to create localized messages.
164
+ #### `.clone()`
165
+ Creates an independent copy of the current builder with all its messages.
61
166
 
62
- - **locales**: `readonly string[]`Allowed locale keys (e.g. `['ja', 'en'] as const`).
63
- - **fallbackLocale**: fallback locale when the active locale value is missing. New messages start with this locale active.
167
+ - Returns: `ChainBuilder<Locales, Messages>`A new builder instance with cloned messages
64
168
 
65
- Overloads:
66
- - `defineMessage<Record<L[number], string>>() -> I18nMessage<L, void>`
67
- - `defineMessage<Record<L[number], Template<C>>>() -> I18nMessage<L, C>`
169
+ ```ts
170
+ const builder1 = createI18n(['ja', 'en'] as const)
171
+ .add({
172
+ title: { ja: 'タイトル', en: 'Title' },
173
+ });
68
174
 
69
- ### I18nMessage<L, C>
175
+ const builder2 = builder1.clone().add({
176
+ greeting: { ja: 'こんにちは', en: 'Hello' },
177
+ });
178
+
179
+ const messages1 = builder1.build('ja');
180
+ const messages2 = builder2.build('ja');
181
+
182
+ console.log(messages1.title.render()); // "タイトル"
183
+ console.log(messages1.greeting); // undefined
184
+
185
+ console.log(messages2.title.render()); // "タイトル"
186
+ console.log(messages2.greeting.render()); // "こんにちは"
187
+ ```
188
+
189
+ **Note**: `clone()` creates a deep copy of all messages, allowing you to branch off from a base builder and add different messages independently.
190
+
191
+ ### `I18nMessage<Locales, Context, ReturnType = string>`
70
192
  Represents a single localized message.
71
193
 
72
- - **properties**
73
- - `locales: L`
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.
194
+ #### Properties
195
+ - `locales: Locales` — Readonly array of allowed locales
196
+ - `locale: Locale` — Current active locale (getter)
197
+ - `data: Record<Locale, Template<Context, ReturnType>>` — Message data for all locales (getter)
198
+
199
+ #### Methods
200
+ - `setLocale(locale: Locale): this` — Sets the active locale
201
+ - `setData(data: Record<Locale, Template<Context, ReturnType>>): this` — Sets the message data
202
+ - `render(ctx?: Context): ReturnType` — Renders the message for the active locale and returns the specified type
203
+
204
+ ```ts
205
+ const message = messages.title;
206
+ console.log(message.locale); // Current locale
207
+ console.log(message.render()); // Rendered string
208
+
209
+ message.setLocale('ja');
210
+ console.log(message.render()); // Japanese version
211
+ ```
212
+
213
+ ### `bindLocale(obj, locale)`
214
+ Recursively traverses objects/arrays and sets the given locale on all `I18nMessage` instances and builds all `ChainBuilder` instances encountered.
81
215
 
82
- ### applyLocale(obj, locale)
83
- Recursively traverses arrays/objects and sets the given `locale` on all `I18nMessage` instances encountered.
216
+ - **obj**: Any object/array structure containing messages or builders
217
+ - **locale**: The locale to apply
218
+ - Returns: A new structure with locale applied (containers are cloned, message instances are updated in place)
219
+
220
+ ```ts
221
+ const data = {
222
+ common: builder1,
223
+ nested: {
224
+ special: builder2,
225
+ },
226
+ };
227
+
228
+ const localized = bindLocale(data, 'en');
229
+ console.log(localized.common.title.render()); // English version
230
+ console.log(localized.nested.special.msg.render()); // English version
231
+ ```
84
232
 
85
- - Returns a new container (arrays/objects are cloned), but reuses the same message instances after updating their locale.
233
+ **Note**: `bindLocale` works with both `ChainBuilder` instances (automatically building them with the specified locale) and already-built message objects (updating their locale).
86
234
 
87
235
  ## Types
88
236
 
89
237
  ```ts
90
- export type Template<C> = string | ((ctx: C) => string);
238
+ export type Template<C, R = string> = R | ((ctx: C) => R);
239
+ export type LocalizedMessage<Locales, Context, ReturnType = string> = I18nMessage<Locales, Context, ReturnType>;
91
240
  ```
92
241
 
93
242
  ## Exports
94
243
 
95
244
  ```ts
245
+ export { createI18n, ChainBuilder } from 'canopy-i18n';
96
246
  export { I18nMessage, isI18nMessage } from 'canopy-i18n';
97
- export { createI18n } from 'canopy-i18n';
98
- export { applyLocale } from 'canopy-i18n';
99
- export type { Template } from 'canopy-i18n';
100
- export type { LocalizedMessage } from 'canopy-i18n';
247
+ export { bindLocale, isChainBuilder } from 'canopy-i18n';
248
+ export type { Template, LocalizedMessage } from 'canopy-i18n';
101
249
  ```
102
250
 
103
- ## Notes
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).
251
+ ## Usage Patterns
108
252
 
109
- ### Split files example (namespace import)
110
-
111
- Import all message exports as a namespace and set the locale across the whole tree.
253
+ ### React Components as Messages
112
254
 
113
255
  ```ts
114
- // messages.ts
115
256
  import { createI18n } from 'canopy-i18n';
116
- const defineMessage = createI18n(['ja', 'en'] as const, 'ja');
117
257
 
118
- export const title = defineMessage({
119
- ja: 'タイトルテスト',
120
- en: 'Title Test',
121
- });
258
+ // Static React components
259
+ const messages = createI18n(['ja', 'en'] as const)
260
+ .add<JSX.Element>({
261
+ badge: {
262
+ ja: <span style={{ background: '#4caf50', color: 'white', padding: '4px 8px', borderRadius: '4px' }}>新着</span>,
263
+ en: <span style={{ background: '#4caf50', color: 'white', padding: '4px 8px', borderRadius: '4px' }}>NEW</span>,
264
+ },
265
+ alert: {
266
+ ja: <div style={{ background: '#fff3cd', padding: '12px', borderRadius: '4px' }}>⚠️ これは警告です</div>,
267
+ en: <div style={{ background: '#fff3cd', padding: '12px', borderRadius: '4px' }}>⚠️ This is a warning</div>,
268
+ },
269
+ })
270
+ .build('en');
271
+
272
+ // Render in React
273
+ function MyComponent() {
274
+ return (
275
+ <div>
276
+ {messages.badge.render()}
277
+ {messages.alert.render()}
278
+ </div>
279
+ );
280
+ }
281
+
282
+ // Dynamic React components with context
283
+ type ButtonContext = {
284
+ onClick: () => void;
285
+ text: string;
286
+ };
122
287
 
123
- export const msg = defineMessage<{ name: string; age: number }>({
124
- ja: c => `こんにちは、${c.name}さん。あなたは${c.age}歳です。`,
125
- en: c => `Hello, ${c.name}. You are ${c.age} years old.`,
126
- });
288
+ const dynamicMessages = createI18n(['ja', 'en'] as const)
289
+ .addTemplates<ButtonContext, JSX.Element>()({
290
+ button: {
291
+ ja: (ctx) => (
292
+ <button onClick={ctx.onClick} style={{ background: '#2196f3', color: 'white', padding: '8px 16px' }}>
293
+ {ctx.text}
294
+ </button>
295
+ ),
296
+ en: (ctx) => (
297
+ <button onClick={ctx.onClick} style={{ background: '#2196f3', color: 'white', padding: '8px 16px' }}>
298
+ {ctx.text}
299
+ </button>
300
+ ),
301
+ },
302
+ })
303
+ .build('en');
304
+
305
+ // Use with context
306
+ function AnotherComponent() {
307
+ return <div>{dynamicMessages.button.render({ onClick: () => alert('Clicked!'), text: 'Click me' })}</div>;
308
+ }
127
309
  ```
128
310
 
129
- ```ts
130
- // usage.ts
131
- import * as messages from './messages';
132
- import { applyLocale } from 'canopy-i18n';
311
+ ### Custom Object Types
133
312
 
134
- const m = applyLocale(messages, 'en');
313
+ ```ts
314
+ type MenuItem = {
315
+ label: string;
316
+ url: string;
317
+ icon: string;
318
+ };
135
319
 
136
- console.log(m.title.render()); // "Title Test"
137
- console.log(m.msg.render({ name: 'Tanaka', age: 20 }));
320
+ const menuMessages = createI18n(['ja', 'en'] as const)
321
+ .add<MenuItem>({
322
+ home: {
323
+ ja: { label: 'ホーム', url: '/', icon: '🏠' },
324
+ en: { label: 'Home', url: '/', icon: '🏠' },
325
+ },
326
+ settings: {
327
+ ja: { label: '設定', url: '/settings', icon: '⚙️' },
328
+ en: { label: 'Settings', url: '/settings', icon: '⚙️' },
329
+ },
330
+ })
331
+ .build('en');
332
+
333
+ const homeMenu = menuMessages.home.render();
334
+ console.log(homeMenu.label); // "Home"
335
+ console.log(homeMenu.url); // "/"
336
+ console.log(homeMenu.icon); // "🏠"
138
337
  ```
139
338
 
140
- #### Multi-file structure
339
+ ### Basic String Messages
141
340
 
142
341
  ```ts
143
- // i18n/defineMessage.ts
144
- import { createI18n } from 'canopy-i18n';
145
- export const defineMessage = createI18n(['ja', 'en'] as const, 'ja');
342
+ const messages = createI18n(['ja', 'en'] as const)
343
+ .add({
344
+ title: { ja: 'タイトル', en: 'Title' },
345
+ greeting: { ja: 'こんにちは', en: 'Hello' },
346
+ farewell: { ja: 'さようなら', en: 'Goodbye' },
347
+ })
348
+ .build('en');
349
+
350
+ console.log(messages.title.render()); // "Title"
351
+ console.log(messages.greeting.render()); // "Hello"
146
352
  ```
147
353
 
354
+ ### Template Functions with Context
355
+
148
356
  ```ts
149
- // i18n/messages/common.ts
150
- import { defineMessage } from '../defineMessage';
151
- export const hello = defineMessage({ ja: 'こんにちは', en: 'Hello' });
357
+ const messages = createI18n(['ja', 'en'] as const)
358
+ .addTemplates<{ name: string; age: number }>()({
359
+ profile: {
360
+ ja: (ctx) => `名前: ${ctx.name}、年齢: ${ctx.age}歳`,
361
+ en: (ctx) => `Name: ${ctx.name}, Age: ${ctx.age}`,
362
+ },
363
+ })
364
+ .build('en');
365
+
366
+ console.log(messages.profile.render({ name: 'Taro', age: 25 }));
367
+ // "Name: Taro, Age: 25"
152
368
  ```
153
369
 
370
+ ### Mixing String and Template Messages
371
+
154
372
  ```ts
155
- // i18n/messages/home.ts
156
- import { defineMessage } from '../defineMessage';
157
- export const title = defineMessage({ ja: 'タイトル', en: 'Title' });
373
+ const messages = createI18n(['ja', 'en'] as const)
374
+ .add({
375
+ title: { ja: 'タイトル', en: 'Title' },
376
+ })
377
+ .addTemplates<{ count: number }>()({
378
+ items: {
379
+ ja: (ctx) => `${ctx.count}個のアイテム`,
380
+ en: (ctx) => `${ctx.count} items`,
381
+ },
382
+ })
383
+ .build('ja');
384
+
385
+ console.log(messages.title.render()); // "タイトル"
386
+ console.log(messages.items.render({ count: 5 })); // "5個のアイテム"
158
387
  ```
159
388
 
389
+ ### Using Clone for Shared Base Messages
390
+
160
391
  ```ts
161
- // i18n/messages/index.ts
162
- export * as common from './common';
163
- export * as home from './home';
392
+ // Create a base builder with common messages
393
+ const baseBuilder = createI18n(['ja', 'en'] as const)
394
+ .add({
395
+ common: { ja: '共通', en: 'Common' },
396
+ error: { ja: 'エラー', en: 'Error' },
397
+ });
398
+
399
+ // Clone and extend for admin pages
400
+ const adminMessages = baseBuilder.clone()
401
+ .add({
402
+ adminTitle: { ja: '管理画面', en: 'Admin Panel' },
403
+ })
404
+ .build('en');
405
+
406
+ // Clone and extend for user pages
407
+ const userMessages = baseBuilder.clone()
408
+ .add({
409
+ userTitle: { ja: 'ユーザー画面', en: 'User Panel' },
410
+ })
411
+ .build('en');
412
+
413
+ console.log(adminMessages.common.render()); // "Common"
414
+ console.log(adminMessages.adminTitle.render()); // "Admin Panel"
415
+ console.log(userMessages.common.render()); // "Common"
416
+ console.log(userMessages.userTitle.render()); // "User Panel"
164
417
  ```
165
418
 
419
+ ### Namespace Pattern (Split Files)
420
+
166
421
  ```ts
167
- // usage.ts
168
- import * as msgs from './i18n/messages';
169
- import { applyLocale } from 'canopy-i18n';
422
+ // i18n/locales.ts
423
+ export const LOCALES = ['ja', 'en'] as const;
170
424
 
171
- const m = applyLocale(msgs, 'en');
425
+ // i18n/common.ts
426
+ import { createI18n } from 'canopy-i18n';
427
+ import { LOCALES } from './locales';
172
428
 
173
- console.log(m.common.hello.render()); // "Hello"
174
- console.log(m.home.title.render()); // "Title"
175
- ```
429
+ export const common = createI18n(LOCALES).add({
430
+ hello: { ja: 'こんにちは', en: 'Hello' },
431
+ goodbye: { ja: 'さようなら', en: 'Goodbye' },
432
+ });
176
433
 
177
- Note: Module namespace objects are read-only; `applyLocale` returns a cloned plain object while updating each `I18nMessage` instance's locale in place.
434
+ // i18n/user.ts
435
+ import { createI18n } from 'canopy-i18n';
436
+ import { LOCALES } from './locales';
178
437
 
179
- ## Example: Next.js App Router
438
+ export const user = createI18n(LOCALES).addTemplates<{ name: string }>()({
439
+ welcome: {
440
+ ja: (ctx) => `ようこそ、${ctx.name}さん`,
441
+ en: (ctx) => `Welcome, ${ctx.name}`,
442
+ },
443
+ });
180
444
 
181
- An example Next.js App Router project lives under `examples/next-app`.
445
+ // i18n/index.ts
446
+ export { common } from './common';
447
+ export { user } from './user';
182
448
 
183
- - Server-side usage: `/{locale}/server` renders messages using `applyLocale` in a server component
184
- - Client-side usage: `/{locale}/client` renders messages using hooks (`useLocale`, `useApplyLocale`)
449
+ // app.ts
450
+ import { bindLocale } from 'canopy-i18n';
451
+ import * as i18n from './i18n';
185
452
 
186
- How to run:
453
+ const messages = bindLocale(i18n, 'en');
454
+ console.log(messages.common.hello.render()); // "Hello"
455
+ console.log(messages.user.welcome.render({ name: 'John' })); // "Welcome, John"
456
+ ```
187
457
 
188
- ```bash
189
- git clone https://github.com/mohhh-ok/canopy-i18n
190
- cd canopy-i18n/examples/next-app
191
- pnpm install
192
- pnpm dev
458
+ ### Dynamic Locale Switching
459
+
460
+ ```ts
461
+ const builder = createI18n(['ja', 'en'] as const)
462
+ .add({
463
+ title: { ja: 'タイトル', en: 'Title' },
464
+ });
465
+
466
+ // Build different locale versions from the same builder
467
+ const jaMessages = builder.build('ja');
468
+ const enMessages = builder.build('en');
469
+
470
+ console.log(jaMessages.title.render()); // "タイトル"
471
+ console.log(enMessages.title.render()); // "Title"
472
+
473
+ // Or use bindLocale to switch locale dynamically
474
+ const messages = builder.build();
475
+ const localizedJa = bindLocale(messages, 'ja');
476
+ const localizedEn = bindLocale(messages, 'en');
477
+ ```
478
+
479
+ ### Deep Nested Structures
480
+
481
+ ```ts
482
+ const structure = {
483
+ header: createI18n(['ja', 'en'] as const)
484
+ .add({ title: { ja: 'ヘッダー', en: 'Header' } }),
485
+ content: {
486
+ main: createI18n(['ja', 'en'] as const)
487
+ .add({ body: { ja: '本文', en: 'Body' } }),
488
+ sidebar: createI18n(['ja', 'en'] as const)
489
+ .add({ widget: { ja: 'ウィジェット', en: 'Widget' } }),
490
+ },
491
+ };
492
+
493
+ const localized = bindLocale(structure, 'en');
494
+ console.log(localized.header.title.render()); // "Header"
495
+ console.log(localized.content.main.body.render()); // "Body"
496
+ console.log(localized.content.sidebar.widget.render()); // "Widget"
193
497
  ```
194
498
 
195
- Open `http://localhost:3000` and you will be redirected to `/{locale}` based on `Accept-Language`.
499
+
500
+ ## Repository
501
+
502
+ 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, 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.applyLocale = applyLocale;
3
+ exports.isChainBuilder = isChainBuilder;
4
+ exports.bindLocale = bindLocale;
5
+ const chainBuilder_1 = require("./chainBuilder");
4
6
  const message_1 = require("./message");
5
- function applyLocale(obj, locale) {
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 === 'object') {
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,31 @@
1
+ import { I18nMessage } from "./message";
2
+ import type { LocalizedMessage } from "./message";
3
+ export declare class ChainBuilder<const Ls extends readonly string[], Messages extends Record<string, I18nMessage<Ls, any, any>> = {}> {
4
+ private readonly locales;
5
+ private messages;
6
+ constructor(locales: Ls, messages?: Messages);
7
+ /**
8
+ * 複数のメッセージを一度に追加
9
+ * 型パラメータRでカスタム型も指定可能(デフォルトはstring)
10
+ */
11
+ add<R = string, Entries extends Record<string, Record<Ls[number], R>> = Record<string, Record<Ls[number], R>>>(entries: {
12
+ [K in keyof Entries]: K extends keyof Messages ? never : Entries[K];
13
+ }): ChainBuilder<Ls, Messages & {
14
+ [K in keyof Entries]: LocalizedMessage<Ls, void, R>;
15
+ }>;
16
+ /**
17
+ * 関数指定版: 複数のテンプレート関数を一度に追加(型は統一)
18
+ */
19
+ addTemplates<C, R = string>(): <Entries extends Record<string, Record<Ls[number], (ctx: C) => R>>>(entries: {
20
+ [K in keyof Entries]: K extends keyof Messages ? never : Entries[K];
21
+ }) => ChainBuilder<Ls, Messages & {
22
+ [K in keyof Entries]: LocalizedMessage<Ls, C, R>;
23
+ }>;
24
+ private deepCloneWithLocale;
25
+ build(locale?: Ls[number]): Messages;
26
+ /**
27
+ * 現在のChainBuilderの状態をコピーした新しいインスタンスを返す
28
+ */
29
+ clone(): ChainBuilder<Ls, Messages>;
30
+ }
31
+ export declare function createI18n<const Ls extends readonly string[]>(locales: Ls): ChainBuilder<Ls, {}>;
@@ -0,0 +1,85 @@
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
+ * 型パラメータRでカスタム型も指定可能(デフォルトはstring)
16
+ */
17
+ add(entries) {
18
+ const newMessages = { ...this.messages };
19
+ for (const [key, data] of Object.entries(entries)) {
20
+ const msg = new message_1.I18nMessage(this.locales, this.locales[0]).setData(data);
21
+ newMessages[key] = msg;
22
+ }
23
+ return new ChainBuilder(this.locales, newMessages);
24
+ }
25
+ /**
26
+ * 関数指定版: 複数のテンプレート関数を一度に追加(型は統一)
27
+ */
28
+ addTemplates() {
29
+ return (entries) => {
30
+ const newMessages = { ...this.messages };
31
+ for (const [key, data] of Object.entries(entries)) {
32
+ const msg = new message_1.I18nMessage(this.locales, this.locales[0]).setData(data);
33
+ newMessages[key] = msg;
34
+ }
35
+ return new ChainBuilder(this.locales, newMessages);
36
+ };
37
+ }
38
+ deepCloneWithLocale(obj, locale) {
39
+ if ((0, message_1.isI18nMessage)(obj)) {
40
+ const cloned = Object.create(Object.getPrototypeOf(obj));
41
+ Object.assign(cloned, obj);
42
+ cloned.setLocale(locale);
43
+ return cloned;
44
+ }
45
+ if (Array.isArray(obj)) {
46
+ return obj.map(v => this.deepCloneWithLocale(v, locale));
47
+ }
48
+ if (obj && typeof obj === "object") {
49
+ const out = {};
50
+ for (const k of Object.keys(obj)) {
51
+ out[k] = this.deepCloneWithLocale(obj[k], locale);
52
+ }
53
+ return out;
54
+ }
55
+ return obj;
56
+ }
57
+ build(locale) {
58
+ if (locale !== undefined) {
59
+ return this.deepCloneWithLocale(this.messages, locale);
60
+ }
61
+ return this.messages;
62
+ }
63
+ /**
64
+ * 現在のChainBuilderの状態をコピーした新しいインスタンスを返す
65
+ */
66
+ clone() {
67
+ const clonedMessages = {};
68
+ for (const [key, msg] of Object.entries(this.messages)) {
69
+ if ((0, message_1.isI18nMessage)(msg)) {
70
+ // I18nMessageをクローン
71
+ const cloned = Object.create(Object.getPrototypeOf(msg));
72
+ Object.assign(cloned, msg);
73
+ clonedMessages[key] = cloned;
74
+ }
75
+ else {
76
+ clonedMessages[key] = msg;
77
+ }
78
+ }
79
+ return new ChainBuilder(this.locales, clonedMessages);
80
+ }
81
+ }
82
+ exports.ChainBuilder = ChainBuilder;
83
+ function createI18n(locales) {
84
+ return new ChainBuilder(locales);
85
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { applyLocale } from './applyLocale';
2
- export { createI18n } from './createI18n';
3
- export { I18nMessage, isI18nMessage } from './message';
4
- export type { LocalizedMessage } from './message';
5
- export type { Template } from './types';
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.applyLocale = void 0;
4
- var applyLocale_1 = require("./applyLocale");
5
- Object.defineProperty(exports, "applyLocale", { enumerable: true, get: function () { return applyLocale_1.applyLocale; } });
6
- var createI18n_1 = require("./createI18n");
7
- Object.defineProperty(exports, "createI18n", { enumerable: true, get: function () { return createI18n_1.createI18n; } });
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
- import { Template } from "./types";
2
- export declare class I18nMessage<Ls extends readonly string[], C> {
1
+ import type { Template } from "./types";
2
+ export type LocalizedMessage<Ls extends readonly string[], C, R = string> = I18nMessage<Ls, C, R>;
3
+ export declare function isI18nMessage(x: unknown): x is I18nMessage<any, any, any>;
4
+ export declare class I18nMessage<Ls extends readonly string[], C, R = string> {
3
5
  readonly locales: Ls;
4
6
  private _locale;
5
- private _fallbackLocale;
6
7
  private _data;
7
- constructor(locales: Ls, fallbackLocale: Ls[number]);
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
- get data(): Record<Ls[number], Template<C>>;
13
- setData(data: Record<Ls[number], Template<C>>): this;
14
- render(this: I18nMessage<Ls, void>): string;
15
- render(ctx: C): string;
11
+ get data(): Record<Ls[number], Template<C, R>>;
12
+ setData(data: Record<Ls[number], Template<C, R>>): this;
13
+ render(this: I18nMessage<Ls, void, R>): R;
14
+ render(ctx: C): R;
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
- function isTemplateFunction(t) {
6
- return typeof t === 'function';
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, fallbackLocale) {
13
+ constructor(locales, locale) {
14
14
  this.locales = locales;
15
- this._fallbackLocale = fallbackLocale;
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,11 @@ class I18nMessage {
37
29
  return this;
38
30
  }
39
31
  render(ctx) {
40
- const v = this._data[this._locale] ?? this._data[this._fallbackLocale];
41
- return isTemplateFunction(v) ? v(ctx) : v;
32
+ const v = this._data[this._locale];
33
+ if ((0, types_1.isTemplateFunction)(v)) {
34
+ return v(ctx);
35
+ }
36
+ return v;
42
37
  }
43
38
  }
44
39
  exports.I18nMessage = I18nMessage;
45
- function isI18nMessage(x) {
46
- return x instanceof I18nMessage;
47
- }
package/dist/types.d.ts CHANGED
@@ -1 +1,2 @@
1
- export type Template<C> = string | ((ctx: C) => string);
1
+ export type Template<C, R = string> = R | ((ctx: C) => R);
2
+ export declare function isTemplateFunction<C, R = string>(t: Template<C, R>): t is (ctx: C) => R;
package/dist/types.js CHANGED
@@ -1,2 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isTemplateFunction = isTemplateFunction;
4
+ function isTemplateFunction(t) {
5
+ return typeof t === "function";
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canopy-i18n",
3
- "version": "0.1.1",
3
+ "version": "0.3.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.7",
25
- "@types/node": "^24.10.1",
26
- "release-it": "^19.0.6",
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.9"
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
+ }
@@ -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;
@@ -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
- }
@@ -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;
@@ -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
- }
@@ -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
- };
@@ -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
- }
@@ -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
- });