@genrwork/laravel-i18next 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.
Potentially problematic release.
This version of @genrwork/laravel-i18next might be problematic. Click here for more details.
- package/README.md +367 -0
- package/client.d.ts +12 -0
- package/dist/index.cjs +12 -0
- package/dist/index.mjs +2 -0
- package/dist/react.cjs +40 -0
- package/dist/react.mjs +38 -0
- package/dist/shared/create-i18n-BSEwKsCX.mjs +641 -0
- package/dist/shared/create-i18n-WsDK4Z8L.cjs +647 -0
- package/dist/svelte.cjs +77 -0
- package/dist/svelte.mjs +74 -0
- package/dist/types/backend.d.ts +29 -0
- package/dist/types/contrib/get-plural-index.d.ts +15 -0
- package/dist/types/format.d.ts +43 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/interfaces/locale-file.d.ts +8 -0
- package/dist/types/interfaces/options.d.ts +19 -0
- package/dist/types/interfaces/replacements.d.ts +6 -0
- package/dist/types/plugin/helper.d.ts +6 -0
- package/dist/types/plugin/locale.d.ts +11 -0
- package/dist/types/plugin/parser.d.ts +22 -0
- package/dist/types/plugin/sources.d.ts +53 -0
- package/dist/types/react/i18n-provider-props.d.ts +23 -0
- package/dist/types/react/index.d.ts +3 -0
- package/dist/types/react/provider.d.ts +10 -0
- package/dist/types/shared/create-i18n.d.ts +26 -0
- package/dist/types/svelte/index.d.ts +48 -0
- package/dist/types/utils/pluralization.d.ts +9 -0
- package/dist/types/utils/recognizer.d.ts +29 -0
- package/dist/types/utils/replacer.d.ts +9 -0
- package/dist/types/utils/resolver.d.ts +23 -0
- package/dist/types/utils/sources.d.ts +14 -0
- package/dist/types/vite.d.ts +25 -0
- package/dist/types/vue/index.d.ts +21 -0
- package/dist/vite.cjs +315 -0
- package/dist/vite.mjs +310 -0
- package/dist/vue.cjs +39 -0
- package/dist/vue.mjs +34 -0
- package/package.json +155 -0
package/README.md
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# laravel-i18next
|
|
2
|
+
|
|
3
|
+
Use your **Laravel** translation files with [i18next](https://www.i18next.com) in **React**, **Vue 3** or **Svelte** (4 or 5), translated exactly like [Laravel Localization](https://laravel.com/docs/localization) does.
|
|
4
|
+
|
|
5
|
+
Each Laravel language file becomes its own **i18next namespace**: `lang/{locale}.json` is the default namespace, and each `lang/{locale}/{namespace}.php` file (auth, validation, your own...) becomes a namespace of the same name — nested any number of levels deep, too (`lang/{locale}/{ns1}/{ns2}.php` → namespace `ns1/ns2`). Nothing is merged into one big bundle, so components only load and depend on the namespaces they actually use.
|
|
6
|
+
|
|
7
|
+
The Vite plugin can also read from **more than one directory**, in a fixed priority order — e.g. a hand-written frontend-only JSON catalog first, PHP translations second. A namespace present in several of these sources is merged per key, higher priority winning, so a project can move a namespace's strings out of PHP into hand-written JSON one string at a time without breaking the ones not yet moved. See [Sources and precedence](#sources-and-precedence).
|
|
8
|
+
|
|
9
|
+
Components use their framework's own i18next bindings: `useTranslation()` (React, Vue) or the store-based `useTranslation()` this package provides for Svelte. This package supplies:
|
|
10
|
+
|
|
11
|
+
- `LaravelBackend` and `LaravelFormat`, the i18next plugins doing the work: loading the namespaced language files (merging several sources per key when configured), and translating like Laravel (`:name` replacements, `trans_choice()` pluralization).
|
|
12
|
+
- A provider per framework — `LaravelReactI18nProvider` (`/react`), `laravelVueI18n` (`/vue`), `setLaravelI18nContext`/`useTranslation` (`/svelte`) — built on top of the two plugins above, SSR and hydration ready.
|
|
13
|
+
- A Vite plugin (`/vite`) turning PHP translations into JSON, and serving one or more sources of language files to the provider through a virtual module.
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
- One of: React 18/19 + react-i18next >= 15, Vue 3.4+, Svelte 4 or 5
|
|
18
|
+
- i18next >= 24
|
|
19
|
+
- Vite >= 5 (optional; the framework bindings work with any bundler, only `/vite` needs Vite)
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install @genrwork/laravel-i18next i18next
|
|
25
|
+
# React
|
|
26
|
+
npm install react-i18next
|
|
27
|
+
# Vue
|
|
28
|
+
npm install i18next-vue
|
|
29
|
+
# Svelte: nothing else to install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Setup
|
|
33
|
+
|
|
34
|
+
### 1. Vite plugin
|
|
35
|
+
|
|
36
|
+
`vite.config.ts`:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import i18n from '@genrwork/laravel-i18next/vite'; // <-- add this
|
|
40
|
+
|
|
41
|
+
export default defineConfig({
|
|
42
|
+
plugins: [
|
|
43
|
+
laravel({ input: ['resources/css/app.css', 'resources/js/app.ts'], refresh: true }),
|
|
44
|
+
react(), // or vue(), or svelte()
|
|
45
|
+
i18n(), // <-- add this
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
While Vite runs, the plugin converts every `.php` file found under a source directory (at any depth) into a sibling `.json` file (regenerated when the PHP file changes, deleted when the build ends). Keep them out of git:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
lang/*/*.json
|
|
54
|
+
Modules/*/lang/*/*.json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The `sources` option is an ordered list of directories, **highest priority first**; each may hold hand-written `.json` files, `.php` files, or both (but not both for the same namespace — a hand-written file always wins where present, so don't shadow one with a same-named PHP file in the same directory). One `*` wildcard segment is allowed per entry, expanded against every matching directory that exists, e.g. `Modules/*/lang`. Defaults to `['lang']`, matching a single Laravel app with no other sources.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
i18n({
|
|
61
|
+
sources: [
|
|
62
|
+
'resources/js/lang', // hand-written, frontend-only strings -- highest priority
|
|
63
|
+
'Modules/*/lang', // this app's own modules' PHP translations
|
|
64
|
+
'lang', // the app's own PHP translations
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The plugin also serves the language files as `virtual:laravel-i18next/files`: an array, one entry per configured source in the same order, each eagerly loaded in server code (so SSR renders translated) and lazily loaded in client code, one chunk per namespace. Declare its type next to Vite's, e.g. in `resources/js/types/vite-env.d.ts`:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
/// <reference types="vite/client" />
|
|
73
|
+
/// <reference types="@genrwork/laravel-i18next/client" />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Without the Vite plugin, build the same structure yourself: an array of `import.meta.glob('/lang/**/*.json')` calls (lazy) or `import.meta.glob('/lang/**/*.json', { eager: true })` calls (eager), one per source, in priority order — see [Namespaces](#namespaces) and [Sources and precedence](#sources-and-precedence). A single `import.meta.glob()` result (not wrapped in an array) is also accepted, as a one-source project.
|
|
77
|
+
|
|
78
|
+
### 2. Share the locale
|
|
79
|
+
|
|
80
|
+
`app/Http/Middleware/HandleInertiaRequests.php` (or any equivalent for your setup):
|
|
81
|
+
|
|
82
|
+
```php
|
|
83
|
+
public function share(Request $request): array
|
|
84
|
+
{
|
|
85
|
+
return [
|
|
86
|
+
...parent::share($request),
|
|
87
|
+
'locale' => app()->getLocale(),
|
|
88
|
+
// ...
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. Framework setup
|
|
94
|
+
|
|
95
|
+
<details open><summary><strong>React</strong> (Inertia v3, <code>withApp</code>)</summary>
|
|
96
|
+
|
|
97
|
+
`resources/js/app.tsx`:
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { createInertiaApp } from '@inertiajs/react';
|
|
101
|
+
import { LaravelReactI18nProvider } from '@genrwork/laravel-i18next/react';
|
|
102
|
+
import files from 'virtual:laravel-i18next/files';
|
|
103
|
+
|
|
104
|
+
void createInertiaApp({
|
|
105
|
+
strictMode: true,
|
|
106
|
+
withApp(app, { page }) {
|
|
107
|
+
return (
|
|
108
|
+
<LaravelReactI18nProvider locale={page.props.locale as string} fallbackLocale="en" files={files}>
|
|
109
|
+
{app}
|
|
110
|
+
</LaravelReactI18nProvider>
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
</details>
|
|
117
|
+
|
|
118
|
+
<details><summary><strong>React</strong> (Inertia v2, <code>setup</code>)</summary>
|
|
119
|
+
|
|
120
|
+
`resources/js/app.tsx`:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { createInertiaApp } from '@inertiajs/react';
|
|
124
|
+
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
|
125
|
+
import { hydrateRoot } from 'react-dom/client';
|
|
126
|
+
import { LaravelReactI18nProvider } from '@genrwork/laravel-i18next/react';
|
|
127
|
+
import files from 'virtual:laravel-i18next/files';
|
|
128
|
+
|
|
129
|
+
createInertiaApp({
|
|
130
|
+
resolve: (name) => resolvePageComponent(`./pages/${name}.tsx`, import.meta.glob('./pages/**/*.tsx')),
|
|
131
|
+
setup({ el, App, props }) {
|
|
132
|
+
// Without SSR: createRoot(el).render(...)
|
|
133
|
+
hydrateRoot(
|
|
134
|
+
el,
|
|
135
|
+
<LaravelReactI18nProvider locale={props.initialPage.props.locale as string} fallbackLocale="en" files={files}>
|
|
136
|
+
<App {...props} />
|
|
137
|
+
</LaravelReactI18nProvider>
|
|
138
|
+
);
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
With SSR, `resources/js/ssr.tsx` follows the same shape with `render: ReactDOMServer.renderToString` and `createServer()`, as in [Laravel's own SSR guide](https://inertiajs.com/server-side-rendering).
|
|
144
|
+
|
|
145
|
+
</details>
|
|
146
|
+
|
|
147
|
+
<details><summary><strong>Vue 3</strong></summary>
|
|
148
|
+
|
|
149
|
+
`resources/js/app.ts`:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { createInertiaApp } from '@inertiajs/vue3';
|
|
153
|
+
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
|
154
|
+
import { createApp, h } from 'vue';
|
|
155
|
+
import { laravelVueI18n } from '@genrwork/laravel-i18next/vue';
|
|
156
|
+
import files from 'virtual:laravel-i18next/files';
|
|
157
|
+
|
|
158
|
+
createInertiaApp({
|
|
159
|
+
resolve: (name) => resolvePageComponent(`./pages/${name}.vue`, import.meta.glob('./pages/**/*.vue')),
|
|
160
|
+
setup({ el, App, props, plugin }) {
|
|
161
|
+
createApp({ render: () => h(App, props) })
|
|
162
|
+
.use(plugin)
|
|
163
|
+
.use(laravelVueI18n({ locale: props.initialPage.props.locale as string, fallbackLocale: 'en', files }))
|
|
164
|
+
.mount(el);
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
For SSR, use `createSSRApp` instead of `createApp` in `resources/js/ssr.ts`, as in [Inertia's own Vue 3 SSR guide](https://inertiajs.com/server-side-rendering) — everything else stays the same.
|
|
170
|
+
|
|
171
|
+
In components: `import { useTranslation } from '@genrwork/laravel-i18next/vue'` (re-exported from `i18next-vue`), or the global `$t` / `$i18next`.
|
|
172
|
+
|
|
173
|
+
</details>
|
|
174
|
+
|
|
175
|
+
<details><summary><strong>Svelte</strong> (4 or 5)</summary>
|
|
176
|
+
|
|
177
|
+
`resources/js/app.ts` (with [`@inertiajs/svelte`](https://inertiajs.com)):
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { createInertiaApp } from '@inertiajs/svelte';
|
|
181
|
+
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
|
182
|
+
import files from 'virtual:laravel-i18next/files';
|
|
183
|
+
import App from './App.svelte';
|
|
184
|
+
|
|
185
|
+
createInertiaApp({
|
|
186
|
+
resolve: (name) => resolvePageComponent(`./pages/${name}.svelte`, import.meta.glob('./pages/**/*.svelte')),
|
|
187
|
+
setup({ el, App: Page, props }) {
|
|
188
|
+
new App({ target: el, props: { Page, props, files } });
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`App.svelte` (top of the component tree — the locale prop is whatever Inertia passes through `props`):
|
|
194
|
+
|
|
195
|
+
```svelte
|
|
196
|
+
<script lang="ts">
|
|
197
|
+
import { setLaravelI18nContext } from '@genrwork/laravel-i18next/svelte';
|
|
198
|
+
|
|
199
|
+
export let Page;
|
|
200
|
+
export let props;
|
|
201
|
+
export let files;
|
|
202
|
+
|
|
203
|
+
setLaravelI18nContext({ locale: props.locale, fallbackLocale: 'en', files });
|
|
204
|
+
</script>
|
|
205
|
+
|
|
206
|
+
<svelte:component this={Page} {...props} />
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
In any child component:
|
|
210
|
+
|
|
211
|
+
```svelte
|
|
212
|
+
<script lang="ts">
|
|
213
|
+
import { useTranslation } from '@genrwork/laravel-i18next/svelte';
|
|
214
|
+
|
|
215
|
+
const { t } = useTranslation('auth');
|
|
216
|
+
</script>
|
|
217
|
+
|
|
218
|
+
<p>{$t('failed')}</p>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
</details>
|
|
222
|
+
|
|
223
|
+
### Following locale changes
|
|
224
|
+
|
|
225
|
+
The framework setup above only runs once, when the page loads. When Laravel shares another locale after a visit (e.g. the user switched language), change the language from a layout component. React:
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
import { usePage } from '@inertiajs/react';
|
|
229
|
+
import { useEffect } from 'react';
|
|
230
|
+
import { useTranslation } from 'react-i18next';
|
|
231
|
+
|
|
232
|
+
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
|
233
|
+
const { locale } = usePage<{ locale: string }>().props;
|
|
234
|
+
const { i18n } = useTranslation();
|
|
235
|
+
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
if (i18n.language !== locale) i18n.changeLanguage(locale);
|
|
238
|
+
}, [i18n, locale]);
|
|
239
|
+
|
|
240
|
+
return children;
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Vue and Svelte follow the same idea: watch the shared `locale` prop and call `i18next.changeLanguage(locale)` (Vue: `const { i18next } = useTranslation()`; Svelte: the `i18next` instance returned by `setLaravelI18nContext()`/`useTranslation()`).
|
|
245
|
+
|
|
246
|
+
## Sources and precedence
|
|
247
|
+
|
|
248
|
+
With more than one `sources` entry, the same `(locale, namespace)` pair can come from several places at once. They are merged **per key**, highest-priority source winning, rather than one source replacing another wholesale:
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
resources/js/lang/es/settings.json { "Save": "Guardar" }
|
|
252
|
+
lang/es/settings.php ['Save' => 'Guardar (antiguo)', 'profile_updated' => 'Perfil actualizado.']
|
|
253
|
+
|
|
254
|
+
→ namespace "settings", locale "es":
|
|
255
|
+
Save -> "Guardar" (from the higher-priority source)
|
|
256
|
+
profile_updated -> "Perfil actualizado." (only in the lower-priority source, resolves from there)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
This is what lets a namespace migrate from PHP to hand-written JSON one string at a time: move a key, and every key not yet moved keeps resolving from PHP.
|
|
260
|
+
|
|
261
|
+
The merge is a **shallow** merge over already-flattened, dotted keys (`sub_level1.text`, not `{ sub_level1: { text: ... } }`) — since a PHP file is flattened before it ever becomes JSON, this shallow merge is exactly the deep merge Laravel itself performs over the original nested arrays; no separate deep-merge step is needed.
|
|
262
|
+
|
|
263
|
+
Two things worth knowing:
|
|
264
|
+
|
|
265
|
+
- **Ties within one source resolve by path order** (alphabetical, as `import.meta.glob()` returns them) — if a wildcard source matches two directories that both produce the very same `(locale, namespace)` pair (e.g. two modules that happen to define an identically-named namespace), the alphabetically-first path wins, silently. Prefixing a module's own namespaces with its name avoids this in practice.
|
|
266
|
+
- **A namespace segment that itself looks like a locale code is misread as the locale** — see the note on `classify()` in `src/utils/recognizer.ts`. No namespace in a typical app looks like a locale code, so this is a documented edge case, not something the library tries to solve.
|
|
267
|
+
|
|
268
|
+
## Namespaces
|
|
269
|
+
|
|
270
|
+
Every Laravel language file is its own namespace:
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
lang/
|
|
274
|
+
en.json → default namespace ("translation")
|
|
275
|
+
en/
|
|
276
|
+
auth.php → "auth" namespace
|
|
277
|
+
validation.php → "validation" namespace
|
|
278
|
+
teams/
|
|
279
|
+
roles.php → "teams/roles" namespace (nested any number of levels)
|
|
280
|
+
it/
|
|
281
|
+
auth.php → "auth" namespace
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`import.meta.glob('/lang/**/*.json')` (or the `virtual:laravel-i18next/files` module) must be **recursive** so it picks up all of these. A component requests a namespace the same way it would for any i18next app:
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
const { t } = useTranslation('auth'); // React
|
|
288
|
+
const { t } = useTranslation('auth'); // Vue (i18next-vue)
|
|
289
|
+
const { t } = useTranslation('auth'); // Svelte
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Namespaces are not merged with each other: `t('failed')` inside the `auth` namespace only sees `auth.php`'s keys — even with several `sources`, a namespace only ever merges with the SAME namespace from another source (see [Sources and precedence](#sources-and-precedence)), never with a different one. Request several namespaces at once (`useTranslation(['translation', 'auth'])` in React/Vue) to use more than one from the same component — list the default namespace **first** if you call `t()` without an explicit `ns`, since react-i18next/i18next-vue resolve unscoped calls against the first namespace in that list. Passing `{ ns: 'auth' }` per call works regardless of order, and is what the Svelte binding always does (its `useTranslation()` is scoped to one namespace per call).
|
|
293
|
+
|
|
294
|
+
## Provider / plugin options
|
|
295
|
+
|
|
296
|
+
`files`, `locale` and `fallbackLocale` are the same across React, Vue and Svelte:
|
|
297
|
+
|
|
298
|
+
- `files` _(required)_: `virtual:laravel-i18next/files` (an array, one entry per configured source), or your own single `import.meta.glob('/lang/**/*.json')` (lazy) / `import.meta.glob('/lang/**/*.json', { eager: true })` (eager) — a bare map behaves exactly like a one-source array.
|
|
299
|
+
- `locale` _(optional)_: defaults to the `<html lang="">` attribute, or `en`.
|
|
300
|
+
- `fallbackLocale` _(optional)_: used when a translation is missing, or when a namespace has no file for `locale`. Defaults to the `<html lang="">` attribute, or `en`.
|
|
301
|
+
|
|
302
|
+
Every provider/plugin instance creates its own i18next instance, so concurrent SSR requests never share a language. Eagerness is all-or-nothing **across every non-empty source**: with every source eager, every known namespace is preloaded and translated on the first render, synchronously — this is what the Vite plugin always emits for SSR, and what a hand-rolled config should match to keep SSR synchronous. With any source lazy, a namespace is fetched the first time a component requests it (merging still waits for every source's candidate to resolve) — in React, requesting components suspend meanwhile (wrap them in `<Suspense>`, which the provider already does around its children); in Vue and Svelte, use the `ready` value (`i18next-vue` re-renders once loaded; the Svelte `useTranslation()` returns a `ready` store) to show a loading state instead. The `<html lang="">` attribute is set immediately and kept in sync with the language.
|
|
303
|
+
|
|
304
|
+
## Usage
|
|
305
|
+
|
|
306
|
+
### Translating
|
|
307
|
+
|
|
308
|
+
`lang/pt.json` (default namespace):
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
{
|
|
312
|
+
"Welcome!": "Bem-vindo!",
|
|
313
|
+
"Welcome, :name!": "Bem-vindo, :name!"
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
const { t } = useTranslation();
|
|
319
|
+
|
|
320
|
+
t('Welcome!'); // Bem-vindo!
|
|
321
|
+
t('Welcome, :name!', { name: 'Francisco' }); // Bem-vindo, Francisco!
|
|
322
|
+
t('Welcome, :NAME!', { name: 'Francisco' }); // Bem-vindo, FRANCISCO!
|
|
323
|
+
t('Some untranslated'); // Some untranslated
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
`lang/pt/auth.php` → `auth` namespace:
|
|
327
|
+
|
|
328
|
+
```tsx
|
|
329
|
+
const { t } = useTranslation('auth');
|
|
330
|
+
|
|
331
|
+
t('failed'); // PHP translation from lang/pt/auth.php's 'failed' key
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
A missing (or empty) translation is looked up in the fallback language, then the key itself is returned.
|
|
335
|
+
|
|
336
|
+
i18next `{{name}}` interpolation and `$t()` nesting are not applied. Replacements named like an i18next option (`lng`, `ns`, `context`, `defaultValue`...) must be given in `replace`: `t('Hello :lng', { replace: { lng: 'PHP' } })`.
|
|
337
|
+
|
|
338
|
+
### Pluralization
|
|
339
|
+
|
|
340
|
+
Pass the number as `count`:
|
|
341
|
+
|
|
342
|
+
`lang/pt/fruits.php`:
|
|
343
|
+
|
|
344
|
+
```php
|
|
345
|
+
<?php
|
|
346
|
+
return [
|
|
347
|
+
'apple_count' => ':count apple|:count apples',
|
|
348
|
+
'none_some_many' => '{0} There are none|[1,19] There are some|[20,*] There are many',
|
|
349
|
+
];
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
const { t } = useTranslation('fruits');
|
|
354
|
+
|
|
355
|
+
t('apple_count', { count: 1 }); // 1 apple
|
|
356
|
+
t('none_some_many', { count: 19 }); // There are some
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Segments without an explicit range are picked with the plural rules of the language the message actually resolved in — the current language, or the fallback language when the message itself came from there, like Laravel does. This is deliberately not just "does the namespace have a file for the current language": with several `sources`, a namespace can end up with SOME content for a language (enough to make `hasResourceBundle()` true) while the specific message being pluralized still came from the fallback — pluralization follows the message, not the namespace. `_one` / `_other` suffixed keys are not used.
|
|
360
|
+
|
|
361
|
+
## Credits
|
|
362
|
+
|
|
363
|
+
- The translation behavior (replacements, pluralization) mirrors Laravel's own [`__()`](https://laravel.com/docs/localization) and `trans_choice()`.
|
|
364
|
+
|
|
365
|
+
## License
|
|
366
|
+
|
|
367
|
+
MIT © GenrWork
|
package/client.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare module 'virtual:laravel-i18next/files' {
|
|
2
|
+
import type { LocaleFiles } from '@genrwork/laravel-i18next';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Laravel language files, one entry per configured `sources` (see the
|
|
6
|
+
* `/vite` plugin's options), highest priority first. Each entry is loaded
|
|
7
|
+
* eagerly on the server (SSR) and lazily on the client.
|
|
8
|
+
*/
|
|
9
|
+
const files: LocaleFiles[];
|
|
10
|
+
|
|
11
|
+
export default files;
|
|
12
|
+
}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var createI18n = require('./shared/create-i18n-WsDK4Z8L.cjs');
|
|
4
|
+
require('i18next');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
exports.LaravelBackend = createI18n.LaravelBackend;
|
|
9
|
+
exports.LaravelFormat = createI18n.LaravelFormat;
|
|
10
|
+
exports.createI18n = createI18n.createI18n;
|
|
11
|
+
exports.documentLocale = createI18n.documentLocale;
|
|
12
|
+
exports.syncDocumentLang = createI18n.syncDocumentLang;
|
package/dist/index.mjs
ADDED
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var reactI18next = require('react-i18next');
|
|
6
|
+
var createI18n = require('./shared/create-i18n-WsDK4Z8L.cjs');
|
|
7
|
+
require('i18next');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Provides an i18next instance translating Laravel language files to react-i18next.
|
|
11
|
+
*
|
|
12
|
+
* Every provider owns its instance, so concurrent SSR requests do not share a language.
|
|
13
|
+
* With eager files, every namespace is preloaded and translated on the first render.
|
|
14
|
+
* While a lazy namespace loads, the children suspend, which keeps server-rendered
|
|
15
|
+
* HTML in place until it can be hydrated.
|
|
16
|
+
*/ function LaravelReactI18nProvider({ children, files, locale, fallbackLocale }) {
|
|
17
|
+
const [i18n] = react.useState(()=>createI18n.createI18n({
|
|
18
|
+
files,
|
|
19
|
+
locale,
|
|
20
|
+
fallbackLocale
|
|
21
|
+
}));
|
|
22
|
+
react.useEffect(()=>{
|
|
23
|
+
if (locale && locale !== i18n.language) i18n.changeLanguage(locale);
|
|
24
|
+
}, [
|
|
25
|
+
i18n,
|
|
26
|
+
locale
|
|
27
|
+
]);
|
|
28
|
+
react.useEffect(()=>createI18n.syncDocumentLang(i18n), [
|
|
29
|
+
i18n
|
|
30
|
+
]);
|
|
31
|
+
return jsxRuntime.jsx(reactI18next.I18nextProvider, {
|
|
32
|
+
i18n: i18n,
|
|
33
|
+
children: jsxRuntime.jsx(react.Suspense, {
|
|
34
|
+
fallback: null,
|
|
35
|
+
children: children
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
exports.LaravelReactI18nProvider = LaravelReactI18nProvider;
|
package/dist/react.mjs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useState, useEffect, Suspense } from 'react';
|
|
3
|
+
import { I18nextProvider } from 'react-i18next';
|
|
4
|
+
import { c as createI18n, s as syncDocumentLang } from './shared/create-i18n-BSEwKsCX.mjs';
|
|
5
|
+
import 'i18next';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Provides an i18next instance translating Laravel language files to react-i18next.
|
|
9
|
+
*
|
|
10
|
+
* Every provider owns its instance, so concurrent SSR requests do not share a language.
|
|
11
|
+
* With eager files, every namespace is preloaded and translated on the first render.
|
|
12
|
+
* While a lazy namespace loads, the children suspend, which keeps server-rendered
|
|
13
|
+
* HTML in place until it can be hydrated.
|
|
14
|
+
*/ function LaravelReactI18nProvider({ children, files, locale, fallbackLocale }) {
|
|
15
|
+
const [i18n] = useState(()=>createI18n({
|
|
16
|
+
files,
|
|
17
|
+
locale,
|
|
18
|
+
fallbackLocale
|
|
19
|
+
}));
|
|
20
|
+
useEffect(()=>{
|
|
21
|
+
if (locale && locale !== i18n.language) i18n.changeLanguage(locale);
|
|
22
|
+
}, [
|
|
23
|
+
i18n,
|
|
24
|
+
locale
|
|
25
|
+
]);
|
|
26
|
+
useEffect(()=>syncDocumentLang(i18n), [
|
|
27
|
+
i18n
|
|
28
|
+
]);
|
|
29
|
+
return jsx(I18nextProvider, {
|
|
30
|
+
i18n: i18n,
|
|
31
|
+
children: jsx(Suspense, {
|
|
32
|
+
fallback: null,
|
|
33
|
+
children: children
|
|
34
|
+
})
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { LaravelReactI18nProvider };
|