@meursyphus/i18n-llm 0.0.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 +244 -0
- package/dist/actions.d.ts +29 -0
- package/dist/actions.js +37 -0
- package/dist/chunk-OTLHL53Z.js +75 -0
- package/dist/cli/index.js +222 -0
- package/dist/client.js +86 -0
- package/dist/index-KVK-m3p1.d.ts +29 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +122 -0
- package/dist/middleware.d.ts +31 -0
- package/dist/middleware.js +75 -0
- package/llm.txt +254 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 MeurSyphus
|
|
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
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# i18n-llm
|
|
2
|
+
|
|
3
|
+
LLM-friendly Next.js i18n library with type-safe translations.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **LLM-readable documentation**: Copy-paste `llm.txt` to set up i18n instantly
|
|
8
|
+
- **Type-safe translations**: Full TypeScript support with key autocomplete
|
|
9
|
+
- **Variable interpolation**: `t("greeting", { name: "John" })`
|
|
10
|
+
- **Zero dependencies**: Only requires Next.js and React
|
|
11
|
+
- **App Router only**: Built specifically for Next.js 14+ App Router
|
|
12
|
+
- **Server & Client components**: Works seamlessly in both
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @meursyphus/i18n-llm
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### 1. Initialize (CLI)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx i18n-llm init --locales en,ko --default en
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or follow the manual setup in [llm.txt](./llm.txt).
|
|
29
|
+
|
|
30
|
+
### 2. Wrap your app
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// app/[lang]/layout.tsx
|
|
34
|
+
import { TranslationsProvider } from '@meursyphus/i18n-llm';
|
|
35
|
+
|
|
36
|
+
export default async function RootLayout({ children, params }) {
|
|
37
|
+
const { lang } = await params;
|
|
38
|
+
return (
|
|
39
|
+
<html lang={lang}>
|
|
40
|
+
<body>
|
|
41
|
+
<TranslationsProvider locale={lang}>
|
|
42
|
+
{children}
|
|
43
|
+
</TranslationsProvider>
|
|
44
|
+
</body>
|
|
45
|
+
</html>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 3. Use translations
|
|
51
|
+
|
|
52
|
+
**Server Component:**
|
|
53
|
+
```tsx
|
|
54
|
+
import { getTranslations } from '@meursyphus/i18n-llm';
|
|
55
|
+
|
|
56
|
+
export default async function Page({ params }) {
|
|
57
|
+
const { lang } = await params;
|
|
58
|
+
const t = await getTranslations('common', lang);
|
|
59
|
+
|
|
60
|
+
return <h1>{t('greeting', { name: 'World' })}</h1>;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Client Component:**
|
|
65
|
+
```tsx
|
|
66
|
+
'use client';
|
|
67
|
+
import { useTranslations } from '@meursyphus/i18n-llm/client';
|
|
68
|
+
|
|
69
|
+
export function Greeting({ name }) {
|
|
70
|
+
const t = useTranslations('common');
|
|
71
|
+
return <p>{t('greeting', { name })}</p>;
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## API Reference
|
|
76
|
+
|
|
77
|
+
### Server Exports (`i18n-llm`)
|
|
78
|
+
|
|
79
|
+
| Export | Description |
|
|
80
|
+
|--------|-------------|
|
|
81
|
+
| `getTranslations(namespace, locale)` | Get translation function for server components |
|
|
82
|
+
| `TranslationsProvider` | Provider component for client components |
|
|
83
|
+
| `defineI18nConfig(config)` | Define i18n configuration |
|
|
84
|
+
| `getPreferredLanguage(request)` | Get user's preferred language from request |
|
|
85
|
+
|
|
86
|
+
### Client Exports (`i18n-llm/client`)
|
|
87
|
+
|
|
88
|
+
| Export | Description |
|
|
89
|
+
|--------|-------------|
|
|
90
|
+
| `useTranslations(namespace)` | Hook to get translation function |
|
|
91
|
+
| `useCurrentLanguage()` | Hook to get current locale |
|
|
92
|
+
|
|
93
|
+
### Middleware (`i18n-llm/middleware`)
|
|
94
|
+
|
|
95
|
+
| Export | Description |
|
|
96
|
+
|--------|-------------|
|
|
97
|
+
| `defineMiddleware(config)` | Configure i18n middleware |
|
|
98
|
+
| `middleware` | The middleware function to export |
|
|
99
|
+
|
|
100
|
+
### Actions (`i18n-llm/actions`)
|
|
101
|
+
|
|
102
|
+
| Export | Description |
|
|
103
|
+
|--------|-------------|
|
|
104
|
+
| `setLanguagePreference(locale, path)` | Server action to change language |
|
|
105
|
+
| `getLanguagePreference()` | Get stored language preference |
|
|
106
|
+
|
|
107
|
+
## Configuration
|
|
108
|
+
|
|
109
|
+
### i18n.config.ts
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { defineI18nConfig } from '@meursyphus/i18n-llm';
|
|
113
|
+
|
|
114
|
+
export default defineI18nConfig({
|
|
115
|
+
defaultLocale: 'en',
|
|
116
|
+
locales: ['en', 'ko', 'ja'] as const,
|
|
117
|
+
messagesPath: './messages',
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### middleware.ts
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { defineMiddleware, middleware } from '@meursyphus/i18n-llm/middleware';
|
|
125
|
+
|
|
126
|
+
defineMiddleware({
|
|
127
|
+
locales: ['en', 'ko', 'ja'],
|
|
128
|
+
defaultLocale: 'en',
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export { middleware };
|
|
132
|
+
|
|
133
|
+
export const config = {
|
|
134
|
+
matcher: ['/((?!api|_next|.*\\..*).*)'],
|
|
135
|
+
};
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Message Files
|
|
139
|
+
|
|
140
|
+
### Structure
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
messages/
|
|
144
|
+
├── types.ts # Type definitions
|
|
145
|
+
├── en/
|
|
146
|
+
│ └── index.ts # English translations
|
|
147
|
+
├── ko/
|
|
148
|
+
│ └── index.ts # Korean translations
|
|
149
|
+
└── ja/
|
|
150
|
+
└── index.ts # Japanese translations
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Type Definition
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// messages/types.ts
|
|
157
|
+
export interface Messages {
|
|
158
|
+
common: {
|
|
159
|
+
title: string;
|
|
160
|
+
greeting: string; // "Hello, {name}!"
|
|
161
|
+
nav: {
|
|
162
|
+
home: string;
|
|
163
|
+
about: string;
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Translation File
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
// messages/en/index.ts
|
|
173
|
+
import type { Messages } from '../types';
|
|
174
|
+
|
|
175
|
+
const messages: Messages = {
|
|
176
|
+
common: {
|
|
177
|
+
title: 'Welcome',
|
|
178
|
+
greeting: 'Hello, {name}!',
|
|
179
|
+
nav: {
|
|
180
|
+
home: 'Home',
|
|
181
|
+
about: 'About',
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export default messages;
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Variable Interpolation
|
|
190
|
+
|
|
191
|
+
Use `{variableName}` placeholders in your translations:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// Message
|
|
195
|
+
"greeting": "Hello, {name}! You have {count} messages."
|
|
196
|
+
|
|
197
|
+
// Usage
|
|
198
|
+
t('greeting', { name: 'John', count: 5 });
|
|
199
|
+
// Output: "Hello, John! You have 5 messages."
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Language Switcher
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
'use client';
|
|
206
|
+
import { useCurrentLanguage } from '@meursyphus/i18n-llm/client';
|
|
207
|
+
import { setLanguagePreference } from '@meursyphus/i18n-llm/actions';
|
|
208
|
+
import { usePathname } from 'next/navigation';
|
|
209
|
+
|
|
210
|
+
export function LanguageSwitcher() {
|
|
211
|
+
const currentLang = useCurrentLanguage();
|
|
212
|
+
const pathname = usePathname();
|
|
213
|
+
|
|
214
|
+
const handleChange = async (locale: string) => {
|
|
215
|
+
await setLanguagePreference(locale, pathname);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<select value={currentLang} onChange={(e) => handleChange(e.target.value)}>
|
|
220
|
+
<option value="en">English</option>
|
|
221
|
+
<option value="ko">한국어</option>
|
|
222
|
+
<option value="ja">日本語</option>
|
|
223
|
+
</select>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## CLI Commands
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# Initialize i18n-llm in your project
|
|
232
|
+
npx i18n-llm init --locales en,ko --default en
|
|
233
|
+
|
|
234
|
+
# Add a new locale
|
|
235
|
+
npx i18n-llm add-locale ja --name "日本語"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## LLM Setup
|
|
239
|
+
|
|
240
|
+
For AI-assisted setup, copy the contents of [llm.txt](./llm.txt) to your AI assistant.
|
|
241
|
+
|
|
242
|
+
## License
|
|
243
|
+
|
|
244
|
+
MIT
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server action to set the user's language preference.
|
|
3
|
+
* Updates the cookie and redirects to the new locale path.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* 'use client';
|
|
7
|
+
* import { setLanguagePreference } from 'i18n-llm/actions';
|
|
8
|
+
*
|
|
9
|
+
* function LanguageSwitcher() {
|
|
10
|
+
* const handleChange = async (locale: string) => {
|
|
11
|
+
* await setLanguagePreference(locale, window.location.pathname);
|
|
12
|
+
* };
|
|
13
|
+
*
|
|
14
|
+
* return (
|
|
15
|
+
* <select onChange={(e) => handleChange(e.target.value)}>
|
|
16
|
+
* <option value="en">English</option>
|
|
17
|
+
* <option value="ko">한국어</option>
|
|
18
|
+
* </select>
|
|
19
|
+
* );
|
|
20
|
+
* }
|
|
21
|
+
*/
|
|
22
|
+
declare function setLanguagePreference(locale: string, currentPath: string): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Gets the user's stored language preference from cookies.
|
|
25
|
+
* Returns null if no preference is set.
|
|
26
|
+
*/
|
|
27
|
+
declare function getLanguagePreference(): Promise<string | null>;
|
|
28
|
+
|
|
29
|
+
export { getLanguagePreference, setLanguagePreference };
|
package/dist/actions.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import {
|
|
3
|
+
getI18nConfig
|
|
4
|
+
} from "./chunk-OTLHL53Z.js";
|
|
5
|
+
|
|
6
|
+
// src/actions.ts
|
|
7
|
+
import { cookies } from "next/headers";
|
|
8
|
+
import { redirect } from "next/navigation";
|
|
9
|
+
var LANGUAGE_COOKIE_NAME = "preferred-language";
|
|
10
|
+
var COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
|
|
11
|
+
async function setLanguagePreference(locale, currentPath) {
|
|
12
|
+
const config = getI18nConfig();
|
|
13
|
+
if (config && !config.locales.includes(locale)) {
|
|
14
|
+
throw new Error(`i18n-llm: Unsupported locale: ${locale}`);
|
|
15
|
+
}
|
|
16
|
+
const cookieStore = await cookies();
|
|
17
|
+
cookieStore.set(LANGUAGE_COOKIE_NAME, locale, {
|
|
18
|
+
httpOnly: true,
|
|
19
|
+
secure: process.env.NODE_ENV === "production",
|
|
20
|
+
sameSite: "lax",
|
|
21
|
+
maxAge: COOKIE_MAX_AGE,
|
|
22
|
+
path: "/"
|
|
23
|
+
});
|
|
24
|
+
const pathSegments = currentPath.split("/");
|
|
25
|
+
pathSegments[1] = locale;
|
|
26
|
+
const newPath = pathSegments.join("/");
|
|
27
|
+
redirect(newPath);
|
|
28
|
+
}
|
|
29
|
+
async function getLanguagePreference() {
|
|
30
|
+
const cookieStore = await cookies();
|
|
31
|
+
const cookie = cookieStore.get(LANGUAGE_COOKIE_NAME);
|
|
32
|
+
return cookie?.value || null;
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
getLanguagePreference,
|
|
36
|
+
setLanguagePreference
|
|
37
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// src/shared/load-translations.ts
|
|
2
|
+
var cachedConfig = null;
|
|
3
|
+
function setI18nConfig(config) {
|
|
4
|
+
cachedConfig = config;
|
|
5
|
+
}
|
|
6
|
+
function getI18nConfig() {
|
|
7
|
+
return cachedConfig;
|
|
8
|
+
}
|
|
9
|
+
async function loadTranslations(locale) {
|
|
10
|
+
if (!cachedConfig) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
"i18n-llm: Configuration not set. Call setI18nConfig() first or use defineI18nConfig() in your i18n.config.ts file."
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
const { defaultLocale, locales, messagesPath } = cachedConfig;
|
|
16
|
+
const isSupported = locales.includes(locale);
|
|
17
|
+
const localeToLoad = isSupported ? locale : defaultLocale;
|
|
18
|
+
try {
|
|
19
|
+
const messages = await import(
|
|
20
|
+
/* @vite-ignore */
|
|
21
|
+
`${messagesPath}/${localeToLoad}`
|
|
22
|
+
).then((module) => module.default);
|
|
23
|
+
return messages;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error(
|
|
26
|
+
`i18n-llm: Failed to load messages for locale: ${localeToLoad}`,
|
|
27
|
+
error
|
|
28
|
+
);
|
|
29
|
+
if (localeToLoad !== defaultLocale) {
|
|
30
|
+
const fallbackMessages = await import(
|
|
31
|
+
/* @vite-ignore */
|
|
32
|
+
`${messagesPath}/${defaultLocale}`
|
|
33
|
+
).then((module) => module.default);
|
|
34
|
+
return fallbackMessages;
|
|
35
|
+
}
|
|
36
|
+
throw new Error(
|
|
37
|
+
`i18n-llm: Failed to load default locale messages (${defaultLocale}).`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function createMessageLoader(messagesPath, defaultLocale, locales) {
|
|
42
|
+
return async (locale) => {
|
|
43
|
+
const isSupported = locales.includes(locale);
|
|
44
|
+
const localeToLoad = isSupported ? locale : defaultLocale;
|
|
45
|
+
try {
|
|
46
|
+
const messages = await import(
|
|
47
|
+
/* @vite-ignore */
|
|
48
|
+
`${messagesPath}/${localeToLoad}`
|
|
49
|
+
).then((module) => module.default);
|
|
50
|
+
return messages;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(
|
|
53
|
+
`i18n-llm: Failed to load messages for locale: ${localeToLoad}`,
|
|
54
|
+
error
|
|
55
|
+
);
|
|
56
|
+
if (localeToLoad !== defaultLocale) {
|
|
57
|
+
const fallbackMessages = await import(
|
|
58
|
+
/* @vite-ignore */
|
|
59
|
+
`${messagesPath}/${defaultLocale}`
|
|
60
|
+
).then((module) => module.default);
|
|
61
|
+
return fallbackMessages;
|
|
62
|
+
}
|
|
63
|
+
throw new Error(
|
|
64
|
+
`i18n-llm: Failed to load default locale messages (${defaultLocale}).`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export {
|
|
71
|
+
setI18nConfig,
|
|
72
|
+
getI18nConfig,
|
|
73
|
+
loadTranslations,
|
|
74
|
+
createMessageLoader
|
|
75
|
+
};
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// cli/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// cli/commands/init.ts
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
function init(options) {
|
|
10
|
+
const locales = options.locales.split(",").map((l) => l.trim());
|
|
11
|
+
const defaultLocale = options.default;
|
|
12
|
+
console.log("\n\u{1F30D} Initializing i18n-llm...\n");
|
|
13
|
+
const dirs = ["messages", ...locales.map((l) => `messages/${l}`)];
|
|
14
|
+
for (const dir of dirs) {
|
|
15
|
+
if (!fs.existsSync(dir)) {
|
|
16
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
17
|
+
console.log(` \u2713 Created ${dir}/`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const configContent = `import { defineI18nConfig } from '@meursyphus/i18n-llm';
|
|
21
|
+
|
|
22
|
+
export default defineI18nConfig({
|
|
23
|
+
defaultLocale: '${defaultLocale}',
|
|
24
|
+
locales: [${locales.map((l) => `'${l}'`).join(", ")}] as const,
|
|
25
|
+
messagesPath: './messages',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export type Locale = ${locales.map((l) => `'${l}'`).join(" | ")};
|
|
29
|
+
`;
|
|
30
|
+
fs.writeFileSync("i18n.config.ts", configContent);
|
|
31
|
+
console.log(" \u2713 Created i18n.config.ts");
|
|
32
|
+
const middlewareContent = `import { defineMiddleware, middleware } from '@meursyphus/i18n-llm/middleware';
|
|
33
|
+
|
|
34
|
+
defineMiddleware({
|
|
35
|
+
locales: [${locales.map((l) => `'${l}'`).join(", ")}],
|
|
36
|
+
defaultLocale: '${defaultLocale}',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export { middleware };
|
|
40
|
+
|
|
41
|
+
export const config = {
|
|
42
|
+
matcher: ['/((?!api|_next|.*\\\\..*).*)'],
|
|
43
|
+
};
|
|
44
|
+
`;
|
|
45
|
+
fs.writeFileSync("middleware.ts", middlewareContent);
|
|
46
|
+
console.log(" \u2713 Created middleware.ts");
|
|
47
|
+
const typesContent = `export interface Messages {
|
|
48
|
+
common: CommonMessages;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CommonMessages {
|
|
52
|
+
title: string;
|
|
53
|
+
greeting: string; // "Hello, {name}!"
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
fs.writeFileSync("messages/types.ts", typesContent);
|
|
57
|
+
console.log(" \u2713 Created messages/types.ts");
|
|
58
|
+
const messageTemplates = {
|
|
59
|
+
en: `import type { Messages } from '../types';
|
|
60
|
+
|
|
61
|
+
const messages: Messages = {
|
|
62
|
+
common: {
|
|
63
|
+
title: 'Welcome',
|
|
64
|
+
greeting: 'Hello, {name}!',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default messages;
|
|
69
|
+
`,
|
|
70
|
+
ko: `import type { Messages } from '../types';
|
|
71
|
+
|
|
72
|
+
const messages: Messages = {
|
|
73
|
+
common: {
|
|
74
|
+
title: '\uD658\uC601\uD569\uB2C8\uB2E4',
|
|
75
|
+
greeting: '\uC548\uB155\uD558\uC138\uC694, {name}\uB2D8!',
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default messages;
|
|
80
|
+
`,
|
|
81
|
+
ja: `import type { Messages } from '../types';
|
|
82
|
+
|
|
83
|
+
const messages: Messages = {
|
|
84
|
+
common: {
|
|
85
|
+
title: '\u3088\u3046\u3053\u305D',
|
|
86
|
+
greeting: '\u3053\u3093\u306B\u3061\u306F\u3001{name}\u3055\u3093\uFF01',
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export default messages;
|
|
91
|
+
`,
|
|
92
|
+
zh: `import type { Messages } from '../types';
|
|
93
|
+
|
|
94
|
+
const messages: Messages = {
|
|
95
|
+
common: {
|
|
96
|
+
title: '\u6B22\u8FCE',
|
|
97
|
+
greeting: '\u4F60\u597D\uFF0C{name}\uFF01',
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export default messages;
|
|
102
|
+
`
|
|
103
|
+
};
|
|
104
|
+
const defaultTemplate = `import type { Messages } from '../types';
|
|
105
|
+
|
|
106
|
+
const messages: Messages = {
|
|
107
|
+
common: {
|
|
108
|
+
title: 'Welcome',
|
|
109
|
+
greeting: 'Hello, {name}!',
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export default messages;
|
|
114
|
+
`;
|
|
115
|
+
for (const locale of locales) {
|
|
116
|
+
const content = messageTemplates[locale] || defaultTemplate;
|
|
117
|
+
const filePath = path.join("messages", locale, "index.ts");
|
|
118
|
+
fs.writeFileSync(filePath, content);
|
|
119
|
+
console.log(` \u2713 Created ${filePath}`);
|
|
120
|
+
}
|
|
121
|
+
console.log(`
|
|
122
|
+
\u2705 i18n-llm initialized successfully!
|
|
123
|
+
|
|
124
|
+
Next steps:
|
|
125
|
+
1. Move your app/ contents to app/[lang]/
|
|
126
|
+
2. Update your root layout to use TranslationsProvider:
|
|
127
|
+
|
|
128
|
+
import { TranslationsProvider } from '@meursyphus/i18n-llm';
|
|
129
|
+
|
|
130
|
+
export default async function RootLayout({ children, params }) {
|
|
131
|
+
const { lang } = await params;
|
|
132
|
+
return (
|
|
133
|
+
<html lang={lang}>
|
|
134
|
+
<body>
|
|
135
|
+
<TranslationsProvider locale={lang}>
|
|
136
|
+
{children}
|
|
137
|
+
</TranslationsProvider>
|
|
138
|
+
</body>
|
|
139
|
+
</html>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
3. Use translations in your components:
|
|
144
|
+
|
|
145
|
+
// Server Component
|
|
146
|
+
import { getTranslations } from '@meursyphus/i18n-llm';
|
|
147
|
+
const t = await getTranslations('common', lang);
|
|
148
|
+
|
|
149
|
+
// Client Component
|
|
150
|
+
import { useTranslations } from '@meursyphus/i18n-llm/client';
|
|
151
|
+
const t = useTranslations('common');
|
|
152
|
+
`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// cli/commands/add-locale.ts
|
|
156
|
+
import * as fs2 from "fs";
|
|
157
|
+
import * as path2 from "path";
|
|
158
|
+
function addLocale(locale, options) {
|
|
159
|
+
const displayName = options.name || locale.toUpperCase();
|
|
160
|
+
console.log(`
|
|
161
|
+
\u{1F30D} Adding locale: ${locale} (${displayName})
|
|
162
|
+
`);
|
|
163
|
+
if (!fs2.existsSync("messages")) {
|
|
164
|
+
console.error("\u274C Error: messages/ directory not found.");
|
|
165
|
+
console.error(" Run 'npx i18n-llm init' first to set up your project.");
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
const localeDir = path2.join("messages", locale);
|
|
169
|
+
if (fs2.existsSync(localeDir)) {
|
|
170
|
+
console.error(`\u274C Error: Locale '${locale}' already exists.`);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
fs2.mkdirSync(localeDir, { recursive: true });
|
|
174
|
+
console.log(` \u2713 Created ${localeDir}/`);
|
|
175
|
+
const existingLocales = fs2.readdirSync("messages").filter(
|
|
176
|
+
(f) => fs2.statSync(path2.join("messages", f)).isDirectory() && f !== locale
|
|
177
|
+
);
|
|
178
|
+
if (existingLocales.length > 0) {
|
|
179
|
+
const sourceLocale = existingLocales[0];
|
|
180
|
+
const sourceDir = path2.join("messages", sourceLocale);
|
|
181
|
+
const files = fs2.readdirSync(sourceDir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
182
|
+
for (const file of files) {
|
|
183
|
+
const sourcePath = path2.join(sourceDir, file);
|
|
184
|
+
const targetPath = path2.join(localeDir, file);
|
|
185
|
+
const content = fs2.readFileSync(sourcePath, "utf-8");
|
|
186
|
+
const modifiedContent = `// TODO: Translate this file to ${displayName}
|
|
187
|
+
${content}`;
|
|
188
|
+
fs2.writeFileSync(targetPath, modifiedContent);
|
|
189
|
+
console.log(` \u2713 Created ${targetPath}`);
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
const defaultContent = `// TODO: Translate this file to ${displayName}
|
|
193
|
+
import type { Messages } from '../types';
|
|
194
|
+
|
|
195
|
+
const messages: Messages = {
|
|
196
|
+
common: {
|
|
197
|
+
title: 'Welcome',
|
|
198
|
+
greeting: 'Hello, {name}!',
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export default messages;
|
|
203
|
+
`;
|
|
204
|
+
fs2.writeFileSync(path2.join(localeDir, "index.ts"), defaultContent);
|
|
205
|
+
console.log(` \u2713 Created ${path2.join(localeDir, "index.ts")}`);
|
|
206
|
+
}
|
|
207
|
+
console.log(`
|
|
208
|
+
\u2705 Locale '${locale}' added successfully!
|
|
209
|
+
|
|
210
|
+
Next steps:
|
|
211
|
+
1. Update your i18n.config.ts to include '${locale}' in the locales array
|
|
212
|
+
2. Update your middleware.ts to include '${locale}' in the locales array
|
|
213
|
+
3. Translate the message files in messages/${locale}/
|
|
214
|
+
`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// cli/index.ts
|
|
218
|
+
var program = new Command();
|
|
219
|
+
program.name("i18n-llm").description("LLM-friendly i18n library for Next.js").version("0.1.0");
|
|
220
|
+
program.command("init").description("Initialize i18n-llm in your Next.js project").option("-l, --locales <locales>", "Comma-separated list of locales", "en,ko").option("-d, --default <locale>", "Default locale", "en").action(init);
|
|
221
|
+
program.command("add-locale").description("Add a new locale to your project").argument("<locale>", "Locale code to add (e.g., ja, zh, fr)").option("-n, --name <name>", "Display name for the locale").action(addLocale);
|
|
222
|
+
program.parse();
|