@meursyphus/i18n-llm 0.0.1 → 0.1.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 +21 -26
- package/dist/actions.d.ts +8 -4
- package/dist/actions.js +1 -6
- package/dist/cli/index.js +23 -9
- package/dist/client.js +45 -0
- package/dist/{index-KVK-m3p1.d.ts → index-D9guefzL.d.ts} +0 -5
- package/dist/index.d.ts +36 -13
- package/dist/index.js +28 -4
- package/dist/middleware.d.ts +11 -4
- package/dist/middleware.js +16 -38
- package/llm.txt +19 -52
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ LLM-friendly Next.js i18n library with type-safe translations.
|
|
|
10
10
|
- **Zero dependencies**: Only requires Next.js and React
|
|
11
11
|
- **App Router only**: Built specifically for Next.js 14+ App Router
|
|
12
12
|
- **Server & Client components**: Works seamlessly in both
|
|
13
|
+
- **No URL routing**: Language managed via cookies (no `/en`, `/ko` prefixes needed)
|
|
13
14
|
|
|
14
15
|
## Installation
|
|
15
16
|
|
|
@@ -30,15 +31,15 @@ Or follow the manual setup in [llm.txt](./llm.txt).
|
|
|
30
31
|
### 2. Wrap your app
|
|
31
32
|
|
|
32
33
|
```tsx
|
|
33
|
-
// app/
|
|
34
|
-
import { TranslationsProvider } from '@meursyphus/i18n-llm';
|
|
34
|
+
// app/layout.tsx
|
|
35
|
+
import { TranslationsProvider, getLocale } from '@meursyphus/i18n-llm';
|
|
35
36
|
|
|
36
|
-
export default async function RootLayout({ children
|
|
37
|
-
const
|
|
37
|
+
export default async function RootLayout({ children }) {
|
|
38
|
+
const locale = await getLocale();
|
|
38
39
|
return (
|
|
39
|
-
<html lang={
|
|
40
|
+
<html lang={locale}>
|
|
40
41
|
<body>
|
|
41
|
-
<TranslationsProvider
|
|
42
|
+
<TranslationsProvider>
|
|
42
43
|
{children}
|
|
43
44
|
</TranslationsProvider>
|
|
44
45
|
</body>
|
|
@@ -53,9 +54,8 @@ export default async function RootLayout({ children, params }) {
|
|
|
53
54
|
```tsx
|
|
54
55
|
import { getTranslations } from '@meursyphus/i18n-llm';
|
|
55
56
|
|
|
56
|
-
export default async function Page(
|
|
57
|
-
const
|
|
58
|
-
const t = await getTranslations('common', lang);
|
|
57
|
+
export default async function Page() {
|
|
58
|
+
const t = await getTranslations('common');
|
|
59
59
|
|
|
60
60
|
return <h1>{t('greeting', { name: 'World' })}</h1>;
|
|
61
61
|
}
|
|
@@ -74,34 +74,35 @@ export function Greeting({ name }) {
|
|
|
74
74
|
|
|
75
75
|
## API Reference
|
|
76
76
|
|
|
77
|
-
### Server Exports (
|
|
77
|
+
### Server Exports (`@meursyphus/i18n-llm`)
|
|
78
78
|
|
|
79
79
|
| Export | Description |
|
|
80
80
|
|--------|-------------|
|
|
81
|
-
| `getTranslations(namespace
|
|
81
|
+
| `getTranslations(namespace)` | Get translation function for server components |
|
|
82
82
|
| `TranslationsProvider` | Provider component for client components |
|
|
83
|
+
| `getLocale()` | Get current locale from cookie/header |
|
|
83
84
|
| `defineI18nConfig(config)` | Define i18n configuration |
|
|
84
|
-
| `getPreferredLanguage(request)` | Get user's preferred language from request |
|
|
85
85
|
|
|
86
|
-
### Client Exports (
|
|
86
|
+
### Client Exports (`@meursyphus/i18n-llm/client`)
|
|
87
87
|
|
|
88
88
|
| Export | Description |
|
|
89
89
|
|--------|-------------|
|
|
90
90
|
| `useTranslations(namespace)` | Hook to get translation function |
|
|
91
91
|
| `useCurrentLanguage()` | Hook to get current locale |
|
|
92
|
+
| `useSetLanguage()` | Hook to change current language |
|
|
92
93
|
|
|
93
|
-
### Middleware (
|
|
94
|
+
### Middleware (`@meursyphus/i18n-llm/middleware`)
|
|
94
95
|
|
|
95
96
|
| Export | Description |
|
|
96
97
|
|--------|-------------|
|
|
97
98
|
| `defineMiddleware(config)` | Configure i18n middleware |
|
|
98
99
|
| `middleware` | The middleware function to export |
|
|
99
100
|
|
|
100
|
-
### Actions (
|
|
101
|
+
### Actions (`@meursyphus/i18n-llm/actions`)
|
|
101
102
|
|
|
102
103
|
| Export | Description |
|
|
103
104
|
|--------|-------------|
|
|
104
|
-
| `setLanguagePreference(locale
|
|
105
|
+
| `setLanguagePreference(locale)` | Server action to change language |
|
|
105
106
|
| `getLanguagePreference()` | Get stored language preference |
|
|
106
107
|
|
|
107
108
|
## Configuration
|
|
@@ -131,7 +132,7 @@ defineMiddleware({
|
|
|
131
132
|
export { middleware };
|
|
132
133
|
|
|
133
134
|
export const config = {
|
|
134
|
-
matcher: ['/((?!api|_next|.*\\..*).*)'],
|
|
135
|
+
matcher: ['/((?!api|_next/static|_next/image|favicon.ico|.*\\..*).*)'],
|
|
135
136
|
};
|
|
136
137
|
```
|
|
137
138
|
|
|
@@ -203,20 +204,14 @@ t('greeting', { name: 'John', count: 5 });
|
|
|
203
204
|
|
|
204
205
|
```tsx
|
|
205
206
|
'use client';
|
|
206
|
-
import { useCurrentLanguage } from '@meursyphus/i18n-llm/client';
|
|
207
|
-
import { setLanguagePreference } from '@meursyphus/i18n-llm/actions';
|
|
208
|
-
import { usePathname } from 'next/navigation';
|
|
207
|
+
import { useCurrentLanguage, useSetLanguage } from '@meursyphus/i18n-llm/client';
|
|
209
208
|
|
|
210
209
|
export function LanguageSwitcher() {
|
|
211
210
|
const currentLang = useCurrentLanguage();
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
const handleChange = async (locale: string) => {
|
|
215
|
-
await setLanguagePreference(locale, pathname);
|
|
216
|
-
};
|
|
211
|
+
const setLanguage = useSetLanguage();
|
|
217
212
|
|
|
218
213
|
return (
|
|
219
|
-
<select value={currentLang} onChange={(e) =>
|
|
214
|
+
<select value={currentLang} onChange={(e) => setLanguage(e.target.value)}>
|
|
220
215
|
<option value="en">English</option>
|
|
221
216
|
<option value="ko">한국어</option>
|
|
222
217
|
<option value="ja">日本語</option>
|
package/dist/actions.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Server action to set the user's language preference.
|
|
3
|
-
* Updates the cookie
|
|
3
|
+
* Updates the cookie only - no redirect. Use with router.refresh() on client.
|
|
4
4
|
*
|
|
5
5
|
* @example
|
|
6
6
|
* 'use client';
|
|
7
|
-
* import { setLanguagePreference } from 'i18n-llm/actions';
|
|
7
|
+
* import { setLanguagePreference } from '@meursyphus/i18n-llm/actions';
|
|
8
|
+
* import { useRouter } from 'next/navigation';
|
|
8
9
|
*
|
|
9
10
|
* function LanguageSwitcher() {
|
|
11
|
+
* const router = useRouter();
|
|
12
|
+
*
|
|
10
13
|
* const handleChange = async (locale: string) => {
|
|
11
|
-
* await setLanguagePreference(locale
|
|
14
|
+
* await setLanguagePreference(locale);
|
|
15
|
+
* router.refresh();
|
|
12
16
|
* };
|
|
13
17
|
*
|
|
14
18
|
* return (
|
|
@@ -19,7 +23,7 @@
|
|
|
19
23
|
* );
|
|
20
24
|
* }
|
|
21
25
|
*/
|
|
22
|
-
declare function setLanguagePreference(locale: string
|
|
26
|
+
declare function setLanguagePreference(locale: string): Promise<void>;
|
|
23
27
|
/**
|
|
24
28
|
* Gets the user's stored language preference from cookies.
|
|
25
29
|
* Returns null if no preference is set.
|
package/dist/actions.js
CHANGED
|
@@ -5,10 +5,9 @@ import {
|
|
|
5
5
|
|
|
6
6
|
// src/actions.ts
|
|
7
7
|
import { cookies } from "next/headers";
|
|
8
|
-
import { redirect } from "next/navigation";
|
|
9
8
|
var LANGUAGE_COOKIE_NAME = "preferred-language";
|
|
10
9
|
var COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
|
|
11
|
-
async function setLanguagePreference(locale
|
|
10
|
+
async function setLanguagePreference(locale) {
|
|
12
11
|
const config = getI18nConfig();
|
|
13
12
|
if (config && !config.locales.includes(locale)) {
|
|
14
13
|
throw new Error(`i18n-llm: Unsupported locale: ${locale}`);
|
|
@@ -21,10 +20,6 @@ async function setLanguagePreference(locale, currentPath) {
|
|
|
21
20
|
maxAge: COOKIE_MAX_AGE,
|
|
22
21
|
path: "/"
|
|
23
22
|
});
|
|
24
|
-
const pathSegments = currentPath.split("/");
|
|
25
|
-
pathSegments[1] = locale;
|
|
26
|
-
const newPath = pathSegments.join("/");
|
|
27
|
-
redirect(newPath);
|
|
28
23
|
}
|
|
29
24
|
async function getLanguagePreference() {
|
|
30
25
|
const cookieStore = await cookies();
|
package/dist/cli/index.js
CHANGED
|
@@ -122,17 +122,16 @@ export default messages;
|
|
|
122
122
|
\u2705 i18n-llm initialized successfully!
|
|
123
123
|
|
|
124
124
|
Next steps:
|
|
125
|
-
1.
|
|
126
|
-
2. Update your root layout to use TranslationsProvider:
|
|
125
|
+
1. Update your root layout to use TranslationsProvider:
|
|
127
126
|
|
|
128
|
-
import { TranslationsProvider } from '@meursyphus/i18n-llm';
|
|
127
|
+
import { TranslationsProvider, getLocale } from '@meursyphus/i18n-llm';
|
|
129
128
|
|
|
130
|
-
export default async function RootLayout({ children
|
|
131
|
-
const
|
|
129
|
+
export default async function RootLayout({ children }) {
|
|
130
|
+
const locale = await getLocale();
|
|
132
131
|
return (
|
|
133
|
-
<html lang={
|
|
132
|
+
<html lang={locale}>
|
|
134
133
|
<body>
|
|
135
|
-
<TranslationsProvider
|
|
134
|
+
<TranslationsProvider>
|
|
136
135
|
{children}
|
|
137
136
|
</TranslationsProvider>
|
|
138
137
|
</body>
|
|
@@ -140,15 +139,30 @@ Next steps:
|
|
|
140
139
|
);
|
|
141
140
|
}
|
|
142
141
|
|
|
143
|
-
|
|
142
|
+
2. Use translations in your components:
|
|
144
143
|
|
|
145
144
|
// Server Component
|
|
146
145
|
import { getTranslations } from '@meursyphus/i18n-llm';
|
|
147
|
-
const t = await getTranslations('common'
|
|
146
|
+
const t = await getTranslations('common');
|
|
148
147
|
|
|
149
148
|
// Client Component
|
|
150
149
|
import { useTranslations } from '@meursyphus/i18n-llm/client';
|
|
151
150
|
const t = useTranslations('common');
|
|
151
|
+
|
|
152
|
+
3. Add a language switcher:
|
|
153
|
+
|
|
154
|
+
import { useSetLanguage, useCurrentLanguage } from '@meursyphus/i18n-llm/client';
|
|
155
|
+
|
|
156
|
+
function LanguageSwitcher() {
|
|
157
|
+
const currentLang = useCurrentLanguage();
|
|
158
|
+
const setLanguage = useSetLanguage();
|
|
159
|
+
return (
|
|
160
|
+
<select value={currentLang} onChange={(e) => setLanguage(e.target.value)}>
|
|
161
|
+
<option value="en">English</option>
|
|
162
|
+
<option value="ko">\uD55C\uAD6D\uC5B4</option>
|
|
163
|
+
</select>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
152
166
|
`);
|
|
153
167
|
}
|
|
154
168
|
|
package/dist/client.js
CHANGED
|
@@ -78,9 +78,54 @@ function useCurrentLanguage() {
|
|
|
78
78
|
const { locale } = useContext2(TranslationsContext);
|
|
79
79
|
return locale;
|
|
80
80
|
}
|
|
81
|
+
|
|
82
|
+
// src/client/use-set-language.ts
|
|
83
|
+
import { useRouter } from "next/navigation";
|
|
84
|
+
import { useCallback } from "react";
|
|
85
|
+
|
|
86
|
+
// src/actions.ts
|
|
87
|
+
import { cookies } from "next/headers";
|
|
88
|
+
|
|
89
|
+
// src/shared/load-translations.ts
|
|
90
|
+
var cachedConfig = null;
|
|
91
|
+
function getI18nConfig() {
|
|
92
|
+
return cachedConfig;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/actions.ts
|
|
96
|
+
var LANGUAGE_COOKIE_NAME = "preferred-language";
|
|
97
|
+
var COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
|
|
98
|
+
async function setLanguagePreference(locale) {
|
|
99
|
+
const config = getI18nConfig();
|
|
100
|
+
if (config && !config.locales.includes(locale)) {
|
|
101
|
+
throw new Error(`i18n-llm: Unsupported locale: ${locale}`);
|
|
102
|
+
}
|
|
103
|
+
const cookieStore = await cookies();
|
|
104
|
+
cookieStore.set(LANGUAGE_COOKIE_NAME, locale, {
|
|
105
|
+
httpOnly: true,
|
|
106
|
+
secure: process.env.NODE_ENV === "production",
|
|
107
|
+
sameSite: "lax",
|
|
108
|
+
maxAge: COOKIE_MAX_AGE,
|
|
109
|
+
path: "/"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/client/use-set-language.ts
|
|
114
|
+
function useSetLanguage() {
|
|
115
|
+
const router = useRouter();
|
|
116
|
+
const setLanguage = useCallback(
|
|
117
|
+
async (locale) => {
|
|
118
|
+
await setLanguagePreference(locale);
|
|
119
|
+
router.refresh();
|
|
120
|
+
},
|
|
121
|
+
[router]
|
|
122
|
+
);
|
|
123
|
+
return setLanguage;
|
|
124
|
+
}
|
|
81
125
|
export {
|
|
82
126
|
ClientTranslationsProvider,
|
|
83
127
|
TranslationsContext,
|
|
84
128
|
useCurrentLanguage,
|
|
129
|
+
useSetLanguage,
|
|
85
130
|
useTranslations
|
|
86
131
|
};
|
|
@@ -19,11 +19,6 @@ interface TranslationsContextType<TMessages = Messages> {
|
|
|
19
19
|
interface MiddlewareConfig {
|
|
20
20
|
locales: string[];
|
|
21
21
|
defaultLocale: string;
|
|
22
|
-
localePrefix?: "always" | "as-needed" | "never";
|
|
23
|
-
redirects?: Array<{
|
|
24
|
-
from: string;
|
|
25
|
-
to: string;
|
|
26
|
-
}>;
|
|
27
22
|
}
|
|
28
23
|
|
|
29
24
|
export type { InterpolationVariables as I, Messages as M, NestedKeyOf as N, TranslationValue as T, NestedValueOf as a, I18nConfig as b, MiddlewareConfig as c, TranslationsContextType as d };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,38 +1,46 @@
|
|
|
1
|
-
import { M as Messages, N as NestedKeyOf, I as InterpolationVariables, a as NestedValueOf, b as I18nConfig } from './index-
|
|
2
|
-
export { c as MiddlewareConfig, T as TranslationValue, d as TranslationsContextType } from './index-
|
|
1
|
+
import { M as Messages, N as NestedKeyOf, I as InterpolationVariables, a as NestedValueOf, b as I18nConfig } from './index-D9guefzL.js';
|
|
2
|
+
export { c as MiddlewareConfig, T as TranslationValue, d as TranslationsContextType } from './index-D9guefzL.js';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
4
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
5
|
import { NextRequest } from 'next/server';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Gets translations for a specific namespace in server components.
|
|
9
|
+
* Automatically detects locale from cookie if not explicitly provided.
|
|
9
10
|
*
|
|
10
11
|
* @example
|
|
11
|
-
* // In a Server Component
|
|
12
|
-
* const t = await getTranslations('common'
|
|
12
|
+
* // In a Server Component (auto-detect locale)
|
|
13
|
+
* const t = await getTranslations('common');
|
|
13
14
|
* return <h1>{t('title')}</h1>;
|
|
14
15
|
*
|
|
16
|
+
* // With explicit locale
|
|
17
|
+
* const t = await getTranslations('common', 'ko');
|
|
18
|
+
*
|
|
15
19
|
* // With variables
|
|
16
20
|
* return <p>{t('greeting', { name: 'World' })}</p>;
|
|
17
21
|
*/
|
|
18
|
-
declare function getTranslations<TMessages extends Messages, Namespace extends keyof TMessages>(namespace: Namespace, locale
|
|
22
|
+
declare function getTranslations<TMessages extends Messages, Namespace extends keyof TMessages>(namespace: Namespace, locale?: string): Promise<(<Key extends NestedKeyOf<TMessages[Namespace] & object>>(key: Key, variables?: InterpolationVariables) => NestedValueOf<TMessages[Namespace] & object, Key> extends string ? string : NestedValueOf<TMessages[Namespace] & object, Key> extends ReactNode ? ReactNode : string)>;
|
|
19
23
|
|
|
20
24
|
interface TranslationsProviderProps {
|
|
21
25
|
children: ReactNode;
|
|
22
|
-
locale
|
|
26
|
+
/** Optional locale override. If not provided, auto-detects from cookie/header. */
|
|
27
|
+
locale?: string;
|
|
23
28
|
}
|
|
24
29
|
/**
|
|
25
30
|
* Server-side translations provider.
|
|
26
31
|
* Loads translations and passes them to the client context.
|
|
32
|
+
* Automatically detects locale from cookie if not explicitly provided.
|
|
27
33
|
*
|
|
28
34
|
* @example
|
|
29
|
-
* // In your root layout
|
|
30
|
-
*
|
|
31
|
-
*
|
|
35
|
+
* // In your root layout (auto-detect locale)
|
|
36
|
+
* import { TranslationsProvider, getLocale } from '@meursyphus/i18n-llm';
|
|
37
|
+
*
|
|
38
|
+
* export default async function RootLayout({ children }) {
|
|
39
|
+
* const locale = await getLocale();
|
|
32
40
|
* return (
|
|
33
|
-
* <html lang={
|
|
41
|
+
* <html lang={locale}>
|
|
34
42
|
* <body>
|
|
35
|
-
* <TranslationsProvider
|
|
43
|
+
* <TranslationsProvider>
|
|
36
44
|
* {children}
|
|
37
45
|
* </TranslationsProvider>
|
|
38
46
|
* </body>
|
|
@@ -40,7 +48,7 @@ interface TranslationsProviderProps {
|
|
|
40
48
|
* );
|
|
41
49
|
* }
|
|
42
50
|
*/
|
|
43
|
-
declare function TranslationsProvider<TMessages extends Messages = Messages>({ children, locale }: TranslationsProviderProps): Promise<react_jsx_runtime.JSX.Element>;
|
|
51
|
+
declare function TranslationsProvider<TMessages extends Messages = Messages>({ children, locale: localeProp }: TranslationsProviderProps): Promise<react_jsx_runtime.JSX.Element>;
|
|
44
52
|
|
|
45
53
|
/**
|
|
46
54
|
* Gets the user's preferred language from the request.
|
|
@@ -48,6 +56,21 @@ declare function TranslationsProvider<TMessages extends Messages = Messages>({ c
|
|
|
48
56
|
*/
|
|
49
57
|
declare function getPreferredLanguage(request: NextRequest): string;
|
|
50
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Gets the current locale in server components.
|
|
61
|
+
* Reads from cookie first, then falls back to middleware header, then default locale.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* // In a Server Component
|
|
65
|
+
* import { getLocale } from '@meursyphus/i18n-llm';
|
|
66
|
+
*
|
|
67
|
+
* export default async function Page() {
|
|
68
|
+
* const locale = await getLocale();
|
|
69
|
+
* return <html lang={locale}>...</html>;
|
|
70
|
+
* }
|
|
71
|
+
*/
|
|
72
|
+
declare function getLocale(): Promise<string>;
|
|
73
|
+
|
|
51
74
|
/**
|
|
52
75
|
* Sets the i18n configuration for message loading.
|
|
53
76
|
* This should be called once at application startup.
|
|
@@ -96,4 +119,4 @@ declare function defineI18nConfig<T extends readonly string[]>(config: {
|
|
|
96
119
|
messagesPath: string;
|
|
97
120
|
}): I18nConfig;
|
|
98
121
|
|
|
99
|
-
export { I18nConfig, InterpolationVariables, Messages, NestedKeyOf, NestedValueOf, TranslationsProvider, createMessageLoader, defineI18nConfig, getI18nConfig, getPreferredLanguage, getTranslations, loadTranslations, setI18nConfig };
|
|
122
|
+
export { I18nConfig, InterpolationVariables, Messages, NestedKeyOf, NestedValueOf, TranslationsProvider, createMessageLoader, defineI18nConfig, getI18nConfig, getLocale, getPreferredLanguage, getTranslations, loadTranslations, setI18nConfig };
|
package/dist/index.js
CHANGED
|
@@ -26,9 +26,31 @@ function getNestedValue(obj, path) {
|
|
|
26
26
|
return value;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
// src/server/get-locale.ts
|
|
30
|
+
import { cookies, headers } from "next/headers";
|
|
31
|
+
var LANGUAGE_COOKIE_NAME = "preferred-language";
|
|
32
|
+
var LOCALE_HEADER_NAME = "x-i18n-locale";
|
|
33
|
+
async function getLocale() {
|
|
34
|
+
const config = getI18nConfig();
|
|
35
|
+
const defaultLocale = config?.defaultLocale || "en";
|
|
36
|
+
const locales = config?.locales || [defaultLocale];
|
|
37
|
+
const cookieStore = await cookies();
|
|
38
|
+
const cookieValue = cookieStore.get(LANGUAGE_COOKIE_NAME)?.value;
|
|
39
|
+
if (cookieValue && locales.includes(cookieValue)) {
|
|
40
|
+
return cookieValue;
|
|
41
|
+
}
|
|
42
|
+
const headerStore = await headers();
|
|
43
|
+
const headerValue = headerStore.get(LOCALE_HEADER_NAME);
|
|
44
|
+
if (headerValue && locales.includes(headerValue)) {
|
|
45
|
+
return headerValue;
|
|
46
|
+
}
|
|
47
|
+
return defaultLocale;
|
|
48
|
+
}
|
|
49
|
+
|
|
29
50
|
// src/server/get-translations.ts
|
|
30
51
|
async function getTranslations(namespace, locale) {
|
|
31
|
-
const
|
|
52
|
+
const resolvedLocale = locale ?? await getLocale();
|
|
53
|
+
const messages = await loadTranslations(resolvedLocale);
|
|
32
54
|
function t(key, variables) {
|
|
33
55
|
const namespaceMessages = messages[namespace];
|
|
34
56
|
const value = getNestedValue(namespaceMessages, key);
|
|
@@ -63,13 +85,14 @@ function ClientTranslationsProvider({
|
|
|
63
85
|
|
|
64
86
|
// src/server/translations-provider.tsx
|
|
65
87
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
66
|
-
async function TranslationsProvider({ children, locale }) {
|
|
88
|
+
async function TranslationsProvider({ children, locale: localeProp }) {
|
|
89
|
+
const locale = localeProp ?? await getLocale();
|
|
67
90
|
const messages = await loadTranslations(locale);
|
|
68
91
|
return /* @__PURE__ */ jsx2(ClientTranslationsProvider, { messages, locale, children });
|
|
69
92
|
}
|
|
70
93
|
|
|
71
94
|
// src/server/get-preferred-language.ts
|
|
72
|
-
var
|
|
95
|
+
var LANGUAGE_COOKIE_NAME2 = "preferred-language";
|
|
73
96
|
function getPreferredLanguage(request) {
|
|
74
97
|
const config = getI18nConfig();
|
|
75
98
|
if (!config) {
|
|
@@ -79,7 +102,7 @@ function getPreferredLanguage(request) {
|
|
|
79
102
|
return "en";
|
|
80
103
|
}
|
|
81
104
|
const { defaultLocale, locales } = config;
|
|
82
|
-
const cookieValue = request.cookies.get(
|
|
105
|
+
const cookieValue = request.cookies.get(LANGUAGE_COOKIE_NAME2)?.value;
|
|
83
106
|
if (cookieValue && locales.includes(cookieValue)) {
|
|
84
107
|
return cookieValue;
|
|
85
108
|
}
|
|
@@ -115,6 +138,7 @@ export {
|
|
|
115
138
|
createMessageLoader,
|
|
116
139
|
defineI18nConfig,
|
|
117
140
|
getI18nConfig,
|
|
141
|
+
getLocale,
|
|
118
142
|
getPreferredLanguage,
|
|
119
143
|
getTranslations,
|
|
120
144
|
loadTranslations,
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { c as MiddlewareConfig } from './index-
|
|
2
|
+
import { c as MiddlewareConfig } from './index-D9guefzL.js';
|
|
3
3
|
import 'react';
|
|
4
4
|
|
|
5
|
+
declare const LOCALE_HEADER_NAME = "x-i18n-locale";
|
|
5
6
|
/**
|
|
6
7
|
* Defines the i18n middleware configuration.
|
|
7
8
|
* Call this once in your middleware.ts file.
|
|
8
9
|
*
|
|
9
10
|
* @example
|
|
10
11
|
* // middleware.ts
|
|
11
|
-
* import { defineMiddleware, middleware } from 'i18n-llm/middleware';
|
|
12
|
+
* import { defineMiddleware, middleware } from '@meursyphus/i18n-llm/middleware';
|
|
12
13
|
*
|
|
13
14
|
* defineMiddleware({
|
|
14
15
|
* locales: ['en', 'ko', 'ja'],
|
|
@@ -22,10 +23,16 @@ import 'react';
|
|
|
22
23
|
* };
|
|
23
24
|
*/
|
|
24
25
|
declare function defineMiddleware(config: MiddlewareConfig): void;
|
|
26
|
+
/**
|
|
27
|
+
* Gets the middleware configuration.
|
|
28
|
+
* Returns null if not configured.
|
|
29
|
+
*/
|
|
30
|
+
declare function getMiddlewareConfig(): MiddlewareConfig | null;
|
|
25
31
|
/**
|
|
26
32
|
* The i18n middleware function.
|
|
27
|
-
*
|
|
33
|
+
* Sets the locale header based on cookie or browser preference.
|
|
34
|
+
* No URL redirects - locale is managed via global state.
|
|
28
35
|
*/
|
|
29
36
|
declare function middleware(request: NextRequest): NextResponse;
|
|
30
37
|
|
|
31
|
-
export { defineMiddleware, middleware };
|
|
38
|
+
export { LOCALE_HEADER_NAME, defineMiddleware, getMiddlewareConfig, middleware };
|
package/dist/middleware.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
// src/middleware.ts
|
|
2
2
|
import { NextResponse } from "next/server";
|
|
3
3
|
var LANGUAGE_COOKIE_NAME = "preferred-language";
|
|
4
|
+
var LOCALE_HEADER_NAME = "x-i18n-locale";
|
|
4
5
|
var middlewareConfig = null;
|
|
5
6
|
function defineMiddleware(config) {
|
|
6
7
|
middlewareConfig = config;
|
|
7
8
|
}
|
|
9
|
+
function getMiddlewareConfig() {
|
|
10
|
+
return middlewareConfig;
|
|
11
|
+
}
|
|
8
12
|
function middleware(request) {
|
|
9
13
|
if (!middlewareConfig) {
|
|
10
14
|
console.error(
|
|
@@ -12,45 +16,17 @@ function middleware(request) {
|
|
|
12
16
|
);
|
|
13
17
|
return NextResponse.next();
|
|
14
18
|
}
|
|
15
|
-
const {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
const pathnameLocale = locales.find(
|
|
26
|
-
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
|
|
27
|
-
);
|
|
28
|
-
const preferredLocale = getPreferredLocale(request, locales, defaultLocale);
|
|
29
|
-
if (pathname === "/") {
|
|
30
|
-
if (localePrefix === "always") {
|
|
31
|
-
return NextResponse.redirect(
|
|
32
|
-
new URL(`/${preferredLocale}`, request.url)
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
return NextResponse.next();
|
|
36
|
-
}
|
|
37
|
-
if (!pathnameLocale) {
|
|
38
|
-
if (localePrefix === "always") {
|
|
39
|
-
return NextResponse.redirect(
|
|
40
|
-
new URL(`/${preferredLocale}${pathname}`, request.url)
|
|
41
|
-
);
|
|
19
|
+
const { locales, defaultLocale } = middlewareConfig;
|
|
20
|
+
const locale = getPreferredLocale(request, locales, defaultLocale);
|
|
21
|
+
const response = NextResponse.next();
|
|
22
|
+
response.headers.set(LOCALE_HEADER_NAME, locale);
|
|
23
|
+
const requestHeaders = new Headers(request.headers);
|
|
24
|
+
requestHeaders.set(LOCALE_HEADER_NAME, locale);
|
|
25
|
+
return NextResponse.next({
|
|
26
|
+
request: {
|
|
27
|
+
headers: requestHeaders
|
|
42
28
|
}
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
for (const redirect of redirects) {
|
|
46
|
-
const pathWithoutLocale = pathname.replace(`/${pathnameLocale}`, "");
|
|
47
|
-
if (pathWithoutLocale === redirect.from) {
|
|
48
|
-
return NextResponse.redirect(
|
|
49
|
-
new URL(`/${pathnameLocale}${redirect.to}`, request.url)
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return NextResponse.next();
|
|
29
|
+
});
|
|
54
30
|
}
|
|
55
31
|
function getPreferredLocale(request, locales, defaultLocale) {
|
|
56
32
|
const cookieValue = request.cookies.get(LANGUAGE_COOKIE_NAME)?.value;
|
|
@@ -70,6 +46,8 @@ function getPreferredLocale(request, locales, defaultLocale) {
|
|
|
70
46
|
return defaultLocale;
|
|
71
47
|
}
|
|
72
48
|
export {
|
|
49
|
+
LOCALE_HEADER_NAME,
|
|
73
50
|
defineMiddleware,
|
|
51
|
+
getMiddlewareConfig,
|
|
74
52
|
middleware
|
|
75
53
|
};
|
package/llm.txt
CHANGED
|
@@ -39,7 +39,7 @@ defineMiddleware({
|
|
|
39
39
|
export { middleware };
|
|
40
40
|
|
|
41
41
|
export const config = {
|
|
42
|
-
matcher: ['/((?!api|_next|.*\\..*).*)'],
|
|
42
|
+
matcher: ['/((?!api|_next/static|_next/image|favicon.ico|.*\\..*).*)'],
|
|
43
43
|
};
|
|
44
44
|
```
|
|
45
45
|
|
|
@@ -85,45 +85,23 @@ const messages: Messages = {
|
|
|
85
85
|
export default messages;
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
## Step 5: Update
|
|
88
|
+
## Step 5: Update layout.tsx
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Before:
|
|
93
|
-
```
|
|
94
|
-
app/
|
|
95
|
-
├── layout.tsx
|
|
96
|
-
├── page.tsx
|
|
97
|
-
└── about/page.tsx
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
After:
|
|
101
|
-
```
|
|
102
|
-
app/
|
|
103
|
-
└── [lang]/
|
|
104
|
-
├── layout.tsx
|
|
105
|
-
├── page.tsx
|
|
106
|
-
└── about/page.tsx
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## Step 6: Update layout.tsx
|
|
110
|
-
|
|
111
|
-
Replace `app/[lang]/layout.tsx`:
|
|
90
|
+
Update `app/layout.tsx`:
|
|
112
91
|
```typescript
|
|
113
|
-
import { TranslationsProvider } from '@meursyphus/i18n-llm';
|
|
92
|
+
import { TranslationsProvider, getLocale } from '@meursyphus/i18n-llm';
|
|
114
93
|
|
|
115
94
|
interface LayoutProps {
|
|
116
95
|
children: React.ReactNode;
|
|
117
|
-
params: Promise<{ lang: string }>;
|
|
118
96
|
}
|
|
119
97
|
|
|
120
|
-
export default async function RootLayout({ children
|
|
121
|
-
const
|
|
98
|
+
export default async function RootLayout({ children }: LayoutProps) {
|
|
99
|
+
const locale = await getLocale();
|
|
122
100
|
|
|
123
101
|
return (
|
|
124
|
-
<html lang={
|
|
102
|
+
<html lang={locale}>
|
|
125
103
|
<body>
|
|
126
|
-
<TranslationsProvider
|
|
104
|
+
<TranslationsProvider>
|
|
127
105
|
{children}
|
|
128
106
|
</TranslationsProvider>
|
|
129
107
|
</body>
|
|
@@ -132,15 +110,14 @@ export default async function RootLayout({ children, params }: LayoutProps) {
|
|
|
132
110
|
}
|
|
133
111
|
```
|
|
134
112
|
|
|
135
|
-
## Step
|
|
113
|
+
## Step 6: Use translations in components
|
|
136
114
|
|
|
137
115
|
Server Component:
|
|
138
116
|
```typescript
|
|
139
117
|
import { getTranslations } from '@meursyphus/i18n-llm';
|
|
140
118
|
|
|
141
|
-
export default async function Page(
|
|
142
|
-
const
|
|
143
|
-
const t = await getTranslations('common', lang);
|
|
119
|
+
export default async function Page() {
|
|
120
|
+
const t = await getTranslations('common');
|
|
144
121
|
|
|
145
122
|
return (
|
|
146
123
|
<div>
|
|
@@ -164,26 +141,20 @@ export function Greeting({ name }: { name: string }) {
|
|
|
164
141
|
}
|
|
165
142
|
```
|
|
166
143
|
|
|
167
|
-
## Step
|
|
144
|
+
## Step 7: Language Switcher (Optional)
|
|
168
145
|
|
|
169
146
|
Create a language switcher component:
|
|
170
147
|
```typescript
|
|
171
148
|
'use client';
|
|
172
149
|
|
|
173
|
-
import { useCurrentLanguage } from '@meursyphus/i18n-llm/client';
|
|
174
|
-
import { setLanguagePreference } from '@meursyphus/i18n-llm/actions';
|
|
175
|
-
import { usePathname } from 'next/navigation';
|
|
150
|
+
import { useCurrentLanguage, useSetLanguage } from '@meursyphus/i18n-llm/client';
|
|
176
151
|
|
|
177
152
|
export function LanguageSwitcher() {
|
|
178
153
|
const currentLang = useCurrentLanguage();
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
const handleChange = async (locale: string) => {
|
|
182
|
-
await setLanguagePreference(locale, pathname);
|
|
183
|
-
};
|
|
154
|
+
const setLanguage = useSetLanguage();
|
|
184
155
|
|
|
185
156
|
return (
|
|
186
|
-
<select value={currentLang} onChange={(e) =>
|
|
157
|
+
<select value={currentLang} onChange={(e) => setLanguage(e.target.value)}>
|
|
187
158
|
<option value="en">English</option>
|
|
188
159
|
<option value="ko">한국어</option>
|
|
189
160
|
</select>
|
|
@@ -191,20 +162,15 @@ export function LanguageSwitcher() {
|
|
|
191
162
|
}
|
|
192
163
|
```
|
|
193
164
|
|
|
194
|
-
## Step
|
|
165
|
+
## Step 8: Generate Metadata (Optional)
|
|
195
166
|
|
|
196
167
|
Use translations in generateMetadata:
|
|
197
168
|
```typescript
|
|
198
169
|
import { getTranslations } from '@meursyphus/i18n-llm';
|
|
199
170
|
import type { Metadata } from 'next';
|
|
200
171
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
|
206
|
-
const { lang } = await params;
|
|
207
|
-
const t = await getTranslations('common', lang);
|
|
172
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
173
|
+
const t = await getTranslations('common');
|
|
208
174
|
|
|
209
175
|
return {
|
|
210
176
|
title: t('title'),
|
|
@@ -217,6 +183,7 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
|
|
217
183
|
|
|
218
184
|
i18n-llm has been configured successfully.
|
|
219
185
|
The app now supports English (en) and Korean (ko).
|
|
186
|
+
Language is managed via cookies - no URL routing needed.
|
|
220
187
|
|
|
221
188
|
To add more locales:
|
|
222
189
|
1. Add locale to `locales` array in `i18n.config.ts`
|