canopy-i18n 0.0.5 → 0.1.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/README.md +33 -32
- package/dist/applyLocale.d.ts +1 -0
- package/dist/applyLocale.js +24 -0
- package/dist/createI18n.d.ts +6 -0
- package/dist/createI18n.js +10 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5 -5
- package/dist/testData.js +1 -1
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -22,18 +22,18 @@ yarn add canopy-i18n
|
|
|
22
22
|
## Quick start
|
|
23
23
|
|
|
24
24
|
```ts
|
|
25
|
-
import {
|
|
25
|
+
import { createI18n, applyLocale } from 'canopy-i18n';
|
|
26
26
|
|
|
27
27
|
// 1) Declare allowed locales and fallback
|
|
28
|
-
const
|
|
28
|
+
const defineMessage = createI18n(['ja', 'en'] as const, 'ja');
|
|
29
29
|
|
|
30
30
|
// 2) Define messages
|
|
31
|
-
const title =
|
|
31
|
+
const title = defineMessage({
|
|
32
32
|
ja: 'タイトルテスト',
|
|
33
33
|
en: 'Title Test',
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
const msg =
|
|
36
|
+
const msg = defineMessage<{ name: string; age: number }>({
|
|
37
37
|
ja: c => `こんにちは、${c.name}さん。あなたは${c.age}歳です。`,
|
|
38
38
|
en: c => `Hello, ${c.name}. You are ${c.age} years old.`,
|
|
39
39
|
});
|
|
@@ -42,12 +42,12 @@ const msg = builder<{ name: string; age: number }>({
|
|
|
42
42
|
const data = {
|
|
43
43
|
title,
|
|
44
44
|
nested: {
|
|
45
|
-
hello:
|
|
45
|
+
hello: defineMessage({ ja: 'こんにちは', en: 'Hello' }),
|
|
46
46
|
},
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
// 4) Apply locale across the tree
|
|
50
|
-
const localized =
|
|
50
|
+
const localized = applyLocale(data, 'en');
|
|
51
51
|
|
|
52
52
|
console.log(localized.title.render()); // "Title Test"
|
|
53
53
|
console.log(localized.nested.hello.render()); // "Hello"
|
|
@@ -56,15 +56,15 @@ console.log(msg.setLocale('en').render({ name: 'Tanaka', age: 20 }));
|
|
|
56
56
|
|
|
57
57
|
## API
|
|
58
58
|
|
|
59
|
-
###
|
|
60
|
-
Returns a `
|
|
59
|
+
### createI18n(locales, fallbackLocale)
|
|
60
|
+
Returns a `defineMessage` function to create localized messages.
|
|
61
61
|
|
|
62
62
|
- **locales**: `readonly string[]` — Allowed locale keys (e.g. `['ja', 'en'] as const`).
|
|
63
63
|
- **fallbackLocale**: fallback locale when the active locale value is missing. New messages start with this locale active.
|
|
64
64
|
|
|
65
65
|
Overloads:
|
|
66
|
-
- `
|
|
67
|
-
- `
|
|
66
|
+
- `defineMessage<Record<L[number], string>>() -> I18nMessage<L, void>`
|
|
67
|
+
- `defineMessage<Record<L[number], Template<C>>>() -> I18nMessage<L, C>`
|
|
68
68
|
|
|
69
69
|
### I18nMessage<L, C>
|
|
70
70
|
Represents a single localized message.
|
|
@@ -79,7 +79,7 @@ Represents a single localized message.
|
|
|
79
79
|
- `setFallbackLocale(locale: L[number]): this`
|
|
80
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.
|
|
81
81
|
|
|
82
|
-
###
|
|
82
|
+
### applyLocale(obj, locale)
|
|
83
83
|
Recursively traverses arrays/objects and sets the given `locale` on all `I18nMessage` instances encountered.
|
|
84
84
|
|
|
85
85
|
- Returns a new container (arrays/objects are cloned), but reuses the same message instances after updating their locale.
|
|
@@ -94,8 +94,8 @@ export type Template<C> = string | ((ctx: C) => string);
|
|
|
94
94
|
|
|
95
95
|
```ts
|
|
96
96
|
export { I18nMessage, isI18nMessage } from 'canopy-i18n';
|
|
97
|
-
export {
|
|
98
|
-
export {
|
|
97
|
+
export { createI18n } from 'canopy-i18n';
|
|
98
|
+
export { applyLocale } from 'canopy-i18n';
|
|
99
99
|
export type { Template } from 'canopy-i18n';
|
|
100
100
|
export type { LocalizedMessage } from 'canopy-i18n';
|
|
101
101
|
```
|
|
@@ -112,15 +112,15 @@ Import all message exports as a namespace and set the locale across the whole tr
|
|
|
112
112
|
|
|
113
113
|
```ts
|
|
114
114
|
// messages.ts
|
|
115
|
-
import {
|
|
116
|
-
const
|
|
115
|
+
import { createI18n } from 'canopy-i18n';
|
|
116
|
+
const defineMessage = createI18n(['ja', 'en'] as const, 'ja');
|
|
117
117
|
|
|
118
|
-
export const title =
|
|
118
|
+
export const title = defineMessage({
|
|
119
119
|
ja: 'タイトルテスト',
|
|
120
120
|
en: 'Title Test',
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
-
export const msg =
|
|
123
|
+
export const msg = defineMessage<{ name: string; age: number }>({
|
|
124
124
|
ja: c => `こんにちは、${c.name}さん。あなたは${c.age}歳です。`,
|
|
125
125
|
en: c => `Hello, ${c.name}. You are ${c.age} years old.`,
|
|
126
126
|
});
|
|
@@ -129,9 +129,9 @@ export const msg = builder<{ name: string; age: number }>({
|
|
|
129
129
|
```ts
|
|
130
130
|
// usage.ts
|
|
131
131
|
import * as messages from './messages';
|
|
132
|
-
import {
|
|
132
|
+
import { applyLocale } from 'canopy-i18n';
|
|
133
133
|
|
|
134
|
-
const m =
|
|
134
|
+
const m = applyLocale(messages, 'en');
|
|
135
135
|
|
|
136
136
|
console.log(m.title.render()); // "Title Test"
|
|
137
137
|
console.log(m.msg.render({ name: 'Tanaka', age: 20 }));
|
|
@@ -140,21 +140,21 @@ console.log(m.msg.render({ name: 'Tanaka', age: 20 }));
|
|
|
140
140
|
#### Multi-file structure
|
|
141
141
|
|
|
142
142
|
```ts
|
|
143
|
-
// i18n/
|
|
144
|
-
import {
|
|
145
|
-
export const
|
|
143
|
+
// i18n/defineMessage.ts
|
|
144
|
+
import { createI18n } from 'canopy-i18n';
|
|
145
|
+
export const defineMessage = createI18n(['ja', 'en'] as const, 'ja');
|
|
146
146
|
```
|
|
147
147
|
|
|
148
148
|
```ts
|
|
149
149
|
// i18n/messages/common.ts
|
|
150
|
-
import {
|
|
151
|
-
export const hello =
|
|
150
|
+
import { defineMessage } from '../defineMessage';
|
|
151
|
+
export const hello = defineMessage({ ja: 'こんにちは', en: 'Hello' });
|
|
152
152
|
```
|
|
153
153
|
|
|
154
154
|
```ts
|
|
155
155
|
// i18n/messages/home.ts
|
|
156
|
-
import {
|
|
157
|
-
export const title =
|
|
156
|
+
import { defineMessage } from '../defineMessage';
|
|
157
|
+
export const title = defineMessage({ ja: 'タイトル', en: 'Title' });
|
|
158
158
|
```
|
|
159
159
|
|
|
160
160
|
```ts
|
|
@@ -166,27 +166,28 @@ export * as home from './home';
|
|
|
166
166
|
```ts
|
|
167
167
|
// usage.ts
|
|
168
168
|
import * as msgs from './i18n/messages';
|
|
169
|
-
import {
|
|
169
|
+
import { applyLocale } from 'canopy-i18n';
|
|
170
170
|
|
|
171
|
-
const m =
|
|
171
|
+
const m = applyLocale(msgs, 'en');
|
|
172
172
|
|
|
173
173
|
console.log(m.common.hello.render()); // "Hello"
|
|
174
174
|
console.log(m.home.title.render()); // "Title"
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
Note: Module namespace objects are read-only; `
|
|
177
|
+
Note: Module namespace objects are read-only; `applyLocale` returns a cloned plain object while updating each `I18nMessage` instance's locale in place.
|
|
178
178
|
|
|
179
179
|
## Example: Next.js App Router
|
|
180
180
|
|
|
181
181
|
An example Next.js App Router project lives under `examples/next-app`.
|
|
182
182
|
|
|
183
|
-
- Server-side usage: `/{locale}/server` renders messages using `
|
|
184
|
-
- Client-side usage: `/{locale}/client` renders messages using hooks (`useLocale`, `
|
|
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`)
|
|
185
185
|
|
|
186
186
|
How to run:
|
|
187
187
|
|
|
188
188
|
```bash
|
|
189
|
-
|
|
189
|
+
git clone https://github.com/mohhh-ok/canopy-i18n
|
|
190
|
+
cd canopy-i18n/examples/next-app
|
|
190
191
|
pnpm install
|
|
191
192
|
pnpm dev
|
|
192
193
|
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function applyLocale<T extends object>(obj: T, locale: string): T;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyLocale = applyLocale;
|
|
4
|
+
const message_1 = require("./message");
|
|
5
|
+
function applyLocale(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
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
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/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { applyLocale } from './applyLocale';
|
|
2
|
+
export { createI18n } from './createI18n';
|
|
2
3
|
export { I18nMessage, isI18nMessage } from './message';
|
|
3
4
|
export type { LocalizedMessage } from './message';
|
|
4
|
-
export {
|
|
5
|
-
export { applyLocaleDeep } from './applyLocaleDeep';
|
|
5
|
+
export type { Template } from './types';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
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; } });
|
|
4
8
|
var message_1 = require("./message");
|
|
5
9
|
Object.defineProperty(exports, "I18nMessage", { enumerable: true, get: function () { return message_1.I18nMessage; } });
|
|
6
10
|
Object.defineProperty(exports, "isI18nMessage", { enumerable: true, get: function () { return message_1.isI18nMessage; } });
|
|
7
|
-
var builder_1 = require("./builder");
|
|
8
|
-
Object.defineProperty(exports, "createMessageBuilder", { enumerable: true, get: function () { return builder_1.createMessageBuilder; } });
|
|
9
|
-
var applyLocaleDeep_1 = require("./applyLocaleDeep");
|
|
10
|
-
Object.defineProperty(exports, "applyLocaleDeep", { enumerable: true, get: function () { return applyLocaleDeep_1.applyLocaleDeep; } });
|
package/dist/testData.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.hasSentMsg = exports.nested = exports.msg = exports.title = void 0;
|
|
4
4
|
const index_1 = require("./index");
|
|
5
|
-
const builder = (0, index_1.
|
|
5
|
+
const builder = (0, index_1.createI18n)(['ja', 'en'], 'ja');
|
|
6
6
|
exports.title = builder({
|
|
7
7
|
ja: 'タイトルテスト',
|
|
8
8
|
en: 'Title Test'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canopy-i18n",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A tiny, type-safe i18n helper",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,9 +21,11 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@tsconfig/node20": "^20.1.
|
|
25
|
-
"@types/node": "^24.
|
|
26
|
-
"
|
|
24
|
+
"@tsconfig/node20": "^20.1.7",
|
|
25
|
+
"@types/node": "^24.10.1",
|
|
26
|
+
"release-it": "^19.0.6",
|
|
27
|
+
"typescript": "^5.9.3",
|
|
28
|
+
"vitest": "^4.0.9"
|
|
27
29
|
},
|
|
28
30
|
"keywords": [
|
|
29
31
|
"i18n",
|
|
@@ -44,6 +46,8 @@
|
|
|
44
46
|
"scripts": {
|
|
45
47
|
"dev": "tsc --watch",
|
|
46
48
|
"build": "tsc",
|
|
47
|
-
"check": "tsc -p . --noEmit"
|
|
49
|
+
"type-check": "tsc -p . --noEmit",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:watch": "vitest"
|
|
48
52
|
}
|
|
49
53
|
}
|