canopy-i18n 0.8.0 → 0.8.2

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.
Files changed (2) hide show
  1. package/README.md +116 -112
  2. package/package.json +7 -6
package/README.md CHANGED
@@ -7,6 +7,9 @@ A tiny, type-safe i18n library for building localized messages with builder patt
7
7
  - **Type-safe**: Compile-time safety for locale keys with full TypeScript IntelliSense support.
8
8
  - **Flexible templating**: Plain functions support any JavaScript logic, template literals, or formatting library.
9
9
  - **Zero dependencies**: Lightweight with native TypeScript syntax, no custom {{placeholder}} format.
10
+ - **React-ready**: Provider, hooks, and built-in source wrappers for URL hash / search param / pathname / localStorage / Cookie. See [React Integration](#react-integration).
11
+
12
+ > **Using React?** Jump straight to [React Integration](#react-integration) — the Provider, hooks, and ready-made source wrappers cover most app setups out of the box.
10
13
  ## Why Canopy i18n?
11
14
 
12
15
  Traditional i18n libraries require separate JSON files and string-based key lookups:
@@ -110,6 +113,119 @@ console.log(jaMessages.greeting()); // "こんにちは"
110
113
  console.log(jaMessages.welcome({ name: 'Tanaka', age: 20 })); // "こんにちは、Tanakaさん。あなたは20歳です。"
111
114
  ```
112
115
 
116
+ ## React Integration
117
+
118
+ `canopy-i18n/react` provides a tiny factory that returns a Provider, hooks, and a pre-bound `i18n` shorthand — all sharing the same `Locale` type.
119
+
120
+ ```tsx
121
+ // i18n.ts
122
+ import { createI18nReact } from 'canopy-i18n/react';
123
+
124
+ export const LOCALES = ['en', 'ja'] as const;
125
+
126
+ export const { i18n, LocaleProvider, useLocale, useBindLocale } =
127
+ createI18nReact(LOCALES);
128
+
129
+ export const appI18n = i18n({
130
+ title: { en: 'My App', ja: 'マイアプリ' },
131
+ greeting: (ctx: { name: string }) => ({
132
+ en: `Hello, ${ctx.name}!`,
133
+ ja: `こんにちは、${ctx.name}さん!`,
134
+ }),
135
+ });
136
+ ```
137
+
138
+ ```tsx
139
+ // main.tsx
140
+ import { LocaleProvider } from './i18n';
141
+
142
+ <LocaleProvider defaultLocale="en">
143
+ <App />
144
+ </LocaleProvider>
145
+ ```
146
+
147
+ `LocaleProvider` also supports a controlled mode for integrating with URL routing, cookies, or external state:
148
+
149
+ ```tsx
150
+ // Controlled mode: locale is owned by the parent
151
+ <LocaleProvider locale={currentLocale} onLocaleChange={setCurrentLocale}>
152
+ <App />
153
+ </LocaleProvider>
154
+ ```
155
+
156
+ In controlled mode, `setLocale` from `useLocale()` calls your `onLocaleChange` handler instead of mutating internal state.
157
+
158
+ `createI18nReact` also accepts a factory-level `useLocaleSource` for source-driven locale (e.g. external store, URL hook, cookie). When set, the Provider reads the locale from this hook and `setLocale` calls `onLocaleChange` instead of mutating internal state:
159
+
160
+ ```tsx
161
+ export const { LocaleProvider, useLocale } = createI18nReact(LOCALES, {
162
+ useLocaleSource: () => useMyStore((s) => s.locale),
163
+ onLocaleChange: (l) => useMyStore.getState().setLocale(l),
164
+ });
165
+
166
+ <LocaleProvider>
167
+ <App />
168
+ </LocaleProvider>
169
+ ```
170
+
171
+ ### Built-in source wrappers
172
+
173
+ For common sources, `canopy-i18n/react` ships ready-made factories. Each returns the same shape as `createI18nReact` and operates in source-driven mode:
174
+
175
+ ```tsx
176
+ import {
177
+ createHashI18nReact, // URL hash (#ja)
178
+ createSearchI18nReact, // URL search param (?lang=ja, configurable via { param })
179
+ createPathnameI18nReact, // URL pathname prefix (/ja/..., configurable via { basePath })
180
+ createStorageI18nReact, // localStorage (configurable via { key })
181
+ createCookieI18nReact, // Cookie (configurable via { key, maxAge, path, sameSite })
182
+ } from 'canopy-i18n/react';
183
+
184
+ export const { LocaleProvider, useLocale, useBindLocale } =
185
+ createHashI18nReact(LOCALES);
186
+
187
+ // Render <LocaleProvider> with no props.
188
+ ```
189
+
190
+ ```tsx
191
+ // App.tsx
192
+ import { appI18n, useBindLocale, useLocale } from './i18n';
193
+
194
+ function App() {
195
+ const m = useBindLocale({ appI18n });
196
+ const { locale, setLocale } = useLocale();
197
+
198
+ return (
199
+ <div>
200
+ <h1>{m.appI18n.title()}</h1>
201
+ <p>{m.appI18n.greeting({ name: 'Taro' })}</p>
202
+ <button onClick={() => setLocale(locale === 'en' ? 'ja' : 'en')}>
203
+ {locale}
204
+ </button>
205
+ </div>
206
+ );
207
+ }
208
+ ```
209
+
210
+ ### Factory return value
211
+
212
+ ```ts
213
+ const {
214
+ locales, // the LOCALES tuple you passed in
215
+ i18n, // function: i18n(entries) → ChainBuilder bound to LOCALES
216
+ LocaleProvider, // uncontrolled or controlled (see above)
217
+ useLocale, // () => { locale, setLocale }
218
+ useBindLocale, // memoized bindLocale, locale type-checked
219
+ } = createI18nReact(['en', 'ja'] as const);
220
+ ```
221
+
222
+ `i18n` is a shorthand for `ChainBuilder.add` bound to a base builder. Each `i18n({...})` call returns an independent `ChainBuilder`, so you can keep chaining `.add({...}).add({...})` for additional entries.
223
+
224
+ - `useBindLocale(msgsDef)` is memoized per `(msgsDef, locale)` pair.
225
+ - The `Locale` type is derived from the `LOCALES` tuple. Passing a `ChainBuilder` whose locales differ from the Provider's locales is rejected at compile time.
226
+ - `createI18nReact` itself has no built-in persistence. Use a built-in wrapper (`createHash/Search/Pathname/Storage/CookieI18nReact`) for common sources, or pass your own `useLocaleSource` / `onLocaleChange` for anything else.
227
+ - React is a `peerDependency` (`>=18`). Non-React users can ignore the `/react` subpath entirely.
228
+
113
229
  ## API
114
230
 
115
231
  ### `createI18n(locales)`
@@ -344,118 +460,6 @@ console.log(localized.content.main.body()); // "Body"
344
460
  console.log(localized.content.sidebar.widget()); // "Widget"
345
461
  ```
346
462
 
347
-
348
- ## React Integration
349
-
350
- `canopy-i18n/react` provides a tiny factory that returns a Provider, hooks, and a pre-bound `defineMessage` — all sharing the same `Locale` type.
351
-
352
- ```tsx
353
- // i18n.ts
354
- import { createI18nReact } from 'canopy-i18n/react';
355
-
356
- export const LOCALES = ['en', 'ja'] as const;
357
-
358
- export const { i18n, LocaleProvider, useLocale, useBindLocale } =
359
- createI18nReact(LOCALES);
360
-
361
- export const appI18n = i18n({
362
- title: { en: 'My App', ja: 'マイアプリ' },
363
- greeting: (ctx: { name: string }) => ({
364
- en: `Hello, ${ctx.name}!`,
365
- ja: `こんにちは、${ctx.name}さん!`,
366
- }),
367
- });
368
- ```
369
-
370
- ```tsx
371
- // main.tsx
372
- import { LocaleProvider } from './i18n';
373
-
374
- <LocaleProvider defaultLocale="en">
375
- <App />
376
- </LocaleProvider>
377
- ```
378
-
379
- `LocaleProvider` also supports a controlled mode for integrating with URL routing, cookies, or external state:
380
-
381
- ```tsx
382
- // Controlled mode: locale is owned by the parent
383
- <LocaleProvider locale={currentLocale} onLocaleChange={setCurrentLocale}>
384
- <App />
385
- </LocaleProvider>
386
- ```
387
-
388
- In controlled mode, `setLocale` from `useLocale()` calls your `onLocaleChange` handler instead of mutating internal state.
389
-
390
- `createI18nReact` also accepts a factory-level `useLocaleSource` for source-driven locale (e.g. external store, URL hook, cookie). When set, the Provider reads the locale from this hook and `setLocale` calls `onLocaleChange` instead of mutating internal state:
391
-
392
- ```tsx
393
- export const { LocaleProvider, useLocale } = createI18nReact(LOCALES, {
394
- useLocaleSource: () => useMyStore((s) => s.locale),
395
- onLocaleChange: (l) => useMyStore.getState().setLocale(l),
396
- });
397
-
398
- <LocaleProvider>
399
- <App />
400
- </LocaleProvider>
401
- ```
402
-
403
- For common sources, `canopy-i18n/react` ships ready-made factories. Each returns the same shape as `createI18nReact` and operates in source-driven mode:
404
-
405
- ```tsx
406
- import {
407
- createHashI18nReact, // URL hash (#ja)
408
- createSearchI18nReact, // URL search param (?lang=ja, configurable via { param })
409
- createPathnameI18nReact, // URL pathname prefix (/ja/..., configurable via { basePath })
410
- createStorageI18nReact, // localStorage (configurable via { key })
411
- createCookieI18nReact, // Cookie (configurable via { key, maxAge, path, sameSite })
412
- } from 'canopy-i18n/react';
413
-
414
- export const { LocaleProvider, useLocale, useBindLocale } =
415
- createHashI18nReact(LOCALES);
416
-
417
- // Render <LocaleProvider> with no props.
418
- ```
419
-
420
- ```tsx
421
- // App.tsx
422
- import { appI18n, useBindLocale, useLocale } from './i18n';
423
-
424
- function App() {
425
- const m = useBindLocale({ appI18n });
426
- const { locale, setLocale } = useLocale();
427
-
428
- return (
429
- <div>
430
- <h1>{m.appI18n.title()}</h1>
431
- <p>{m.appI18n.greeting({ name: 'Taro' })}</p>
432
- <button onClick={() => setLocale(locale === 'en' ? 'ja' : 'en')}>
433
- {locale}
434
- </button>
435
- </div>
436
- );
437
- }
438
- ```
439
-
440
- ### Factory return value
441
-
442
- ```ts
443
- const {
444
- locales, // the LOCALES tuple you passed in
445
- i18n, // function: i18n(entries) → ChainBuilder bound to LOCALES
446
- LocaleProvider, // uncontrolled or controlled (see above)
447
- useLocale, // () => { locale, setLocale }
448
- useBindLocale, // memoized bindLocale, locale type-checked
449
- } = createI18nReact(['en', 'ja'] as const);
450
- ```
451
-
452
- `i18n` is a shorthand for `ChainBuilder.add` bound to a base builder. Each `i18n({...})` call returns an independent `ChainBuilder`, so you can keep chaining `.add({...}).add({...})` for additional entries.
453
-
454
- - `useBindLocale(msgsDef)` is memoized per `(msgsDef, locale)` pair.
455
- - The `Locale` type is derived from the `LOCALES` tuple. Passing a `ChainBuilder` whose locales differ from the Provider's locales is rejected at compile time.
456
- - `createI18nReact` itself has no built-in persistence. Use a built-in wrapper (`createHash/Search/Pathname/Storage/CookieI18nReact`) for common sources, or pass your own `useLocaleSource` / `onLocaleChange` for anything else.
457
- - React is a `peerDependency` (`>=18`). Non-React users can ignore the `/react` subpath entirely.
458
-
459
463
  ## Repository
460
464
 
461
465
  https://github.com/MOhhh-ok/canopy-i18n
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canopy-i18n",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "The Type-Safe i18n library that your IDE will Love",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -49,16 +49,17 @@
49
49
  "type-check": "tsc -p . --noEmit",
50
50
  "test": "vitest run",
51
51
  "test:watch": "vitest",
52
- "_publish": "npm publish && git push --follow-tags && gh release create \"$(node -p \"require('./package.json').version\")\" --generate-notes"
52
+ "release": "GITHUB_TOKEN=\"$(gh auth token)\" node ./node_modules/release-it/bin/release-it.js"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@tsconfig/node20": "^20.1.9",
56
- "@types/node": "^25.3.0",
57
- "@types/react": "^19.2.14",
56
+ "@types/node": "^25.9.1",
57
+ "@types/react": "^19.2.15",
58
58
  "next": "16",
59
59
  "react": "^19.2.6",
60
- "typescript": "^5.9.3",
61
- "vitest": "^4.0.18"
60
+ "release-it": "^19.2.4",
61
+ "typescript": "^6.0.3",
62
+ "vitest": "^4.1.7"
62
63
  },
63
64
  "keywords": [
64
65
  "i18n",