@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.

Files changed (38) hide show
  1. package/README.md +367 -0
  2. package/client.d.ts +12 -0
  3. package/dist/index.cjs +12 -0
  4. package/dist/index.mjs +2 -0
  5. package/dist/react.cjs +40 -0
  6. package/dist/react.mjs +38 -0
  7. package/dist/shared/create-i18n-BSEwKsCX.mjs +641 -0
  8. package/dist/shared/create-i18n-WsDK4Z8L.cjs +647 -0
  9. package/dist/svelte.cjs +77 -0
  10. package/dist/svelte.mjs +74 -0
  11. package/dist/types/backend.d.ts +29 -0
  12. package/dist/types/contrib/get-plural-index.d.ts +15 -0
  13. package/dist/types/format.d.ts +43 -0
  14. package/dist/types/index.d.ts +7 -0
  15. package/dist/types/interfaces/locale-file.d.ts +8 -0
  16. package/dist/types/interfaces/options.d.ts +19 -0
  17. package/dist/types/interfaces/replacements.d.ts +6 -0
  18. package/dist/types/plugin/helper.d.ts +6 -0
  19. package/dist/types/plugin/locale.d.ts +11 -0
  20. package/dist/types/plugin/parser.d.ts +22 -0
  21. package/dist/types/plugin/sources.d.ts +53 -0
  22. package/dist/types/react/i18n-provider-props.d.ts +23 -0
  23. package/dist/types/react/index.d.ts +3 -0
  24. package/dist/types/react/provider.d.ts +10 -0
  25. package/dist/types/shared/create-i18n.d.ts +26 -0
  26. package/dist/types/svelte/index.d.ts +48 -0
  27. package/dist/types/utils/pluralization.d.ts +9 -0
  28. package/dist/types/utils/recognizer.d.ts +29 -0
  29. package/dist/types/utils/replacer.d.ts +9 -0
  30. package/dist/types/utils/resolver.d.ts +23 -0
  31. package/dist/types/utils/sources.d.ts +14 -0
  32. package/dist/types/vite.d.ts +25 -0
  33. package/dist/types/vue/index.d.ts +21 -0
  34. package/dist/vite.cjs +315 -0
  35. package/dist/vite.mjs +310 -0
  36. package/dist/vue.cjs +39 -0
  37. package/dist/vue.mjs +34 -0
  38. package/package.json +155 -0
package/dist/vite.cjs ADDED
@@ -0,0 +1,315 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var fs = require('fs');
6
+ var path = require('path');
7
+ var vite = require('vite');
8
+ var phpArrayReader = require('php-array-reader');
9
+
10
+ /**
11
+ * Normalize the separators of a directory name and ensure a trailing separator.
12
+ *
13
+ * @param rawDirname
14
+ */ function dirnameSanitize(rawDirname) {
15
+ return rawDirname.replace(/[\\/]+/g, path.sep).replace(/[\\/]+$/, '') + path.sep;
16
+ }
17
+
18
+ /**
19
+ * Whether `dirname` contains at least one `.php` file, at any depth.
20
+ *
21
+ * @param dirname
22
+ */ function hasPhpFile(dirname) {
23
+ return fs.readdirSync(dirname, {
24
+ withFileTypes: true
25
+ }).some((entry)=>{
26
+ if (entry.isDirectory()) return hasPhpFile(path.join(dirname, entry.name));
27
+ return entry.isFile() && entry.name.endsWith('.php');
28
+ });
29
+ }
30
+ var locale = {
31
+ /**
32
+ * Locales having a `lang/{locale}/` directory containing PHP files,
33
+ * however deep -- a locale whose PHP all lives in a nested namespace
34
+ * subdirectory still counts.
35
+ *
36
+ * @param dirname
37
+ */ getPhpLocale: (dirname)=>{
38
+ const sanitizedDirname = dirnameSanitize(dirname);
39
+ if (!fs.existsSync(sanitizedDirname)) {
40
+ return [];
41
+ }
42
+ return fs.readdirSync(sanitizedDirname).filter((folder)=>{
43
+ const fullPath = path.join(sanitizedDirname, folder);
44
+ return fs.statSync(fullPath).isDirectory();
45
+ }).filter((folder)=>hasPhpFile(path.join(sanitizedDirname, folder))).sort();
46
+ }
47
+ };
48
+
49
+ /**
50
+ * Convert every `lang/{locale}/{namespace}.php` file into a sibling
51
+ * `lang/{locale}/{namespace}.json` file, one per PHP file, so each PHP file
52
+ * becomes its own i18next namespace instead of being merged with the others.
53
+ * A PHP file may sit at any depth under the locale directory --
54
+ * `lang/{locale}/{ns1}/{ns2}.php` and deeper -- and gets its sibling `.json`
55
+ * written at that same nested path; the resulting namespace name itself is
56
+ * inferred later, from the generated file's path, by `src/utils/recognizer.ts`.
57
+ *
58
+ * A source meant to hold hand-written JSON (e.g. a frontend-only catalog)
59
+ * should never also hold PHP files of the same name -- this directory is a
60
+ * PHP source, so its sibling `.json` is always regenerated, overwriting
61
+ * whatever was there before (including a leftover from a previous, uncleaned
62
+ * run). Keep hand-written translations in their own source directory.
63
+ *
64
+ * @param dirname
65
+ */ function parser(dirname) {
66
+ const sanitizedDirname = dirnameSanitize(dirname);
67
+ if (!fs.existsSync(sanitizedDirname)) {
68
+ return [];
69
+ }
70
+ return fs.readdirSync(sanitizedDirname).filter((locale)=>fs.statSync(path.join(sanitizedDirname, locale)).isDirectory()).sort().flatMap((locale)=>generateNamespaceFiles(path.join(sanitizedDirname, locale)));
71
+ }
72
+ /**
73
+ * Convert every `.php` file found anywhere under a locale directory into a
74
+ * sibling `.json` file at the same relative path, depth-first and
75
+ * alphabetically within each directory level.
76
+ *
77
+ * @param localeDirname
78
+ */ function generateNamespaceFiles(localeDirname) {
79
+ return walkPhpFiles(localeDirname).sort().map((phpPath)=>{
80
+ const content = fs.readFileSync(phpPath, 'utf-8');
81
+ const translations = convertToDottedKey(phpArrayReader.fromString(content));
82
+ const outputPath = phpPath.replace(/\.php$/, '.json');
83
+ fs.writeFileSync(outputPath, JSON.stringify(translations));
84
+ return {
85
+ basename: path.basename(outputPath),
86
+ path: outputPath
87
+ };
88
+ });
89
+ }
90
+ /**
91
+ * Every `.php` file under `dirname`, at any depth, as absolute paths.
92
+ *
93
+ * @param dirname
94
+ */ function walkPhpFiles(dirname) {
95
+ return fs.readdirSync(dirname, {
96
+ withFileTypes: true
97
+ }).flatMap((entry)=>{
98
+ const entryPath = path.join(dirname, entry.name);
99
+ if (entry.isDirectory()) return walkPhpFiles(entryPath);
100
+ return entry.isFile() && entry.name.endsWith('.php') ? [
101
+ entryPath
102
+ ] : [];
103
+ });
104
+ }
105
+ /**
106
+ * Flatten nested translations into dotted keys, e.g. `sub_level1.sub_level2.text`.
107
+ *
108
+ * @param source
109
+ * @param target
110
+ * @param keys
111
+ */ function convertToDottedKey(source, target = {}, keys = []) {
112
+ Object.entries(source).forEach(([key, value])=>{
113
+ const newPrefix = [
114
+ ...keys,
115
+ key
116
+ ];
117
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
118
+ convertToDottedKey(value, target, newPrefix);
119
+ } else {
120
+ target[newPrefix.join('.')] = value;
121
+ }
122
+ });
123
+ return target;
124
+ }
125
+
126
+ function isDirectory(dir) {
127
+ return fs.existsSync(dir) && fs.statSync(dir).isDirectory();
128
+ }
129
+ function countWildcardSegments(normalizedPattern) {
130
+ return normalizedPattern.split('/').filter((segment)=>segment === '*').length;
131
+ }
132
+ /**
133
+ * Every directory an ordered source `pattern` currently expands to, relative
134
+ * to `root`. A pattern with no `*` is a single literal directory (existing or
135
+ * not -- callers decide what an absent literal directory means). A pattern
136
+ * with exactly one `*` segment (e.g. `Modules/*\/lang`) is expanded against
137
+ * the filesystem: every existing subdirectory of the prefix before the `*`,
138
+ * with the suffix after it appended, kept only when the full path also
139
+ * exists, sorted. Two or more `*` segments throw -- unsupported.
140
+ *
141
+ * @param pattern
142
+ * @param root
143
+ */ function expandDirs(pattern, root) {
144
+ const normalized = pattern.replace(/[\\/]+/g, '/');
145
+ if (countWildcardSegments(normalized) > 1) {
146
+ throw new Error(`laravel-i18next: a source pattern may contain at most one "*" segment, got "${pattern}".`);
147
+ }
148
+ const segments = normalized.split('/').filter(Boolean);
149
+ const wildcardIndex = segments.indexOf('*');
150
+ if (wildcardIndex === -1) {
151
+ const dir = path.resolve(root, normalized);
152
+ return isDirectory(dir) ? [
153
+ dir
154
+ ] : [];
155
+ }
156
+ const prefix = path.resolve(root, segments.slice(0, wildcardIndex).join('/'));
157
+ const suffix = segments.slice(wildcardIndex + 1);
158
+ if (!isDirectory(prefix)) return [];
159
+ return fs.readdirSync(prefix, {
160
+ withFileTypes: true
161
+ }).filter((entry)=>entry.isDirectory()).map((entry)=>path.join(prefix, entry.name, ...suffix)).filter(isDirectory).sort();
162
+ }
163
+ /**
164
+ * The root-relative glob `import.meta.glob()` needs for a source pattern.
165
+ * Globs inside a virtual module have no containing file to be relative to,
166
+ * so this is always root-relative (a leading `/`). A `*` segment in the
167
+ * pattern passes through `path.resolve`/`path.relative`/`normalizePath`
168
+ * completely untouched -- none of them treat it specially -- which is why
169
+ * the emitted glob needs no separate wildcard handling: `Modules/*\/lang`
170
+ * becomes `/Modules/*\/lang/**\/*.json` and `import.meta.glob()` expands the
171
+ * `*` itself, natively, at both dev and build time.
172
+ *
173
+ * @param pattern
174
+ * @param root
175
+ */ function toGlob(pattern, root) {
176
+ const relative = vite.normalizePath(path.relative(root, path.resolve(root, pattern.replace(/[\\/]+/g, '/'))));
177
+ return `/${relative}/**/*.json`;
178
+ }
179
+ /**
180
+ * Whether `file` sits under one of `dirnames` (any depth) -- used to decide
181
+ * whether a changed file belongs to any configured source at all.
182
+ *
183
+ * @param file
184
+ * @param dirnames
185
+ */ function isUnderAnySource(file, dirnames) {
186
+ const normalizedFile = vite.normalizePath(file);
187
+ return dirnames.some((dir)=>normalizedFile.startsWith(`${vite.normalizePath(dir)}/`));
188
+ }
189
+
190
+ /**
191
+ * Language files, one namespace per language file: loaded eagerly by server
192
+ * code (SSR), so it renders translated, and lazily by client code. The
193
+ * default export is an ARRAY, one entry per configured source in `sources`,
194
+ * highest priority first (see `LocaleFileSources` in `src/interfaces/options.ts`).
195
+ */ const FILES_MODULE_ID = 'virtual:laravel-i18next/files';
196
+ const RESOLVED_FILES_MODULE_ID = `\0${FILES_MODULE_ID}`;
197
+ /**
198
+ * Makes Laravel PHP translations available to i18next by generating a sibling
199
+ * `.json` file next to each `.php` file, across one or more ordered sources.
200
+ */ function i18n(config) {
201
+ var _a;
202
+ const logger = vite.createLogger('info', {
203
+ prefix: '[laravel-i18next]'
204
+ });
205
+ const sourcePatterns = (_a = config === null || config === void 0 ? void 0 : config.sources) !== null && _a !== void 0 ? _a : [
206
+ 'lang'
207
+ ];
208
+ let root = process.cwd();
209
+ let generated = [];
210
+ let exitHandlersBound = false;
211
+ function clean() {
212
+ generated.forEach((file)=>fs.existsSync(file.path) && fs.unlinkSync(file.path));
213
+ generated = [];
214
+ }
215
+ /**
216
+ * (Re)generate every source's sibling `.json` files. Run once up front
217
+ * (`config()`) and again whenever a `.php` file changes (`hotUpdate` /
218
+ * `handleHotUpdate`).
219
+ */ function parseAll() {
220
+ let anyPhp = false;
221
+ generated = sourcePatterns.flatMap((pattern)=>{
222
+ const dirnames = expandDirs(pattern, root);
223
+ // A literal (wildcard-free) source that does not exist is very likely
224
+ // a setup mistake -- a wrong path, or (Laravel 9+) `php artisan
225
+ // lang:publish` was never run.
226
+ // A wildcard source matching nothing (no modules yet) is normal, so it
227
+ // stays silent.
228
+ if (dirnames.length === 0 && !pattern.includes('*')) {
229
+ logger.error(`Source "${pattern}" does not exist. Check the "sources" option (Laravel 9+ keeps language files in "lang", older versions in "resources/lang"), or on Laravel 9+ publish them with \`php artisan lang:publish\`.`, {
230
+ timestamp: true
231
+ });
232
+ logger.error('For more information please visit: https://laravel.com/docs/10.x/localization#publishing-the-language-files', {
233
+ timestamp: true
234
+ });
235
+ }
236
+ return dirnames.flatMap((dirname)=>{
237
+ if (locale.getPhpLocale(dirname).length > 0) anyPhp = true;
238
+ return parser(dirname);
239
+ });
240
+ });
241
+ if (!anyPhp) {
242
+ logger.info('No source contains PHP translation files.', {
243
+ timestamp: true
244
+ });
245
+ logger.info('For more information please visit: https://laravel.com/docs/10.x/localization#introduction', {
246
+ timestamp: true
247
+ });
248
+ }
249
+ return generated;
250
+ }
251
+ function isTranslationSource(file) {
252
+ return sourcePatterns.some((pattern)=>isUnderAnySource(file, expandDirs(pattern, root)));
253
+ }
254
+ return {
255
+ name: 'i18n',
256
+ enforce: 'post',
257
+ configResolved (resolvedConfig) {
258
+ root = resolvedConfig.root;
259
+ },
260
+ resolveId: {
261
+ order: 'pre',
262
+ handler (id) {
263
+ return id === FILES_MODULE_ID ? RESOLVED_FILES_MODULE_ID : undefined;
264
+ }
265
+ },
266
+ load (id, options) {
267
+ if (id !== RESOLVED_FILES_MODULE_ID) return undefined;
268
+ // Globs of virtual modules must be relative to the root. Recursive, so
269
+ // each also matches namespaces nested any number of levels deep.
270
+ const entries = sourcePatterns.map((pattern)=>JSON.stringify(toGlob(pattern, root))).map((pattern)=>(options === null || options === void 0 ? void 0 : options.ssr) ? `import.meta.glob(${pattern}, { eager: true })` : `import.meta.glob(${pattern})`).join(', ');
271
+ return `export default [${entries}];`;
272
+ },
273
+ config (userConfig) {
274
+ var _a;
275
+ // `config()` runs before `configResolved()`, so `root` is not yet the
276
+ // fully resolved one here -- close enough for source expansion, and
277
+ // `configResolved` overwrites it before `load()` ever runs.
278
+ root = path.resolve((_a = userConfig === null || userConfig === void 0 ? void 0 : userConfig.root) !== null && _a !== void 0 ? _a : process.cwd());
279
+ parseAll();
280
+ },
281
+ buildEnd: clean,
282
+ // `hotUpdate` (Vite 6+) is the modern hook; `handleHotUpdate` is kept for
283
+ // the `vite >= 5` peer range and is ignored by Vite whenever `hotUpdate`
284
+ // is also defined. Neither hook needs to invalidate the virtual module
285
+ // for an ADDED or DELETED json file, nor for a CHANGED one -- Vite's own
286
+ // `import.meta.glob` HMR already does that, virtual ids included. What
287
+ // is not free: a brand-new `.php` file has no sibling `.json` yet for
288
+ // that glob to notice, so it must be parsed here first.
289
+ hotUpdate (options) {
290
+ var _a;
291
+ if (!isTranslationSource(options.file)) return undefined;
292
+ if (options.file.endsWith('.php')) parseAll();
293
+ const filesModule = this.environment.moduleGraph.getModuleById(RESOLVED_FILES_MODULE_ID);
294
+ if (!filesModule) return undefined;
295
+ return [
296
+ ...(_a = this.environment.moduleGraph.getModulesByFile(options.file)) !== null && _a !== void 0 ? _a : [],
297
+ filesModule
298
+ ];
299
+ },
300
+ handleHotUpdate (ctx) {
301
+ if (isTranslationSource(ctx.file) && ctx.file.endsWith('.php')) parseAll();
302
+ },
303
+ configureServer () {
304
+ if (exitHandlersBound) return;
305
+ process.on('exit', clean);
306
+ process.on('SIGINT', process.exit);
307
+ process.on('SIGTERM', process.exit);
308
+ process.on('SIGHUP', process.exit);
309
+ exitHandlersBound = true;
310
+ }
311
+ };
312
+ }
313
+
314
+ exports.FILES_MODULE_ID = FILES_MODULE_ID;
315
+ exports.default = i18n;
package/dist/vite.mjs ADDED
@@ -0,0 +1,310 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { normalizePath, createLogger } from 'vite';
4
+ import { fromString } from 'php-array-reader';
5
+
6
+ /**
7
+ * Normalize the separators of a directory name and ensure a trailing separator.
8
+ *
9
+ * @param rawDirname
10
+ */ function dirnameSanitize(rawDirname) {
11
+ return rawDirname.replace(/[\\/]+/g, path.sep).replace(/[\\/]+$/, '') + path.sep;
12
+ }
13
+
14
+ /**
15
+ * Whether `dirname` contains at least one `.php` file, at any depth.
16
+ *
17
+ * @param dirname
18
+ */ function hasPhpFile(dirname) {
19
+ return fs.readdirSync(dirname, {
20
+ withFileTypes: true
21
+ }).some((entry)=>{
22
+ if (entry.isDirectory()) return hasPhpFile(path.join(dirname, entry.name));
23
+ return entry.isFile() && entry.name.endsWith('.php');
24
+ });
25
+ }
26
+ var locale = {
27
+ /**
28
+ * Locales having a `lang/{locale}/` directory containing PHP files,
29
+ * however deep -- a locale whose PHP all lives in a nested namespace
30
+ * subdirectory still counts.
31
+ *
32
+ * @param dirname
33
+ */ getPhpLocale: (dirname)=>{
34
+ const sanitizedDirname = dirnameSanitize(dirname);
35
+ if (!fs.existsSync(sanitizedDirname)) {
36
+ return [];
37
+ }
38
+ return fs.readdirSync(sanitizedDirname).filter((folder)=>{
39
+ const fullPath = path.join(sanitizedDirname, folder);
40
+ return fs.statSync(fullPath).isDirectory();
41
+ }).filter((folder)=>hasPhpFile(path.join(sanitizedDirname, folder))).sort();
42
+ }
43
+ };
44
+
45
+ /**
46
+ * Convert every `lang/{locale}/{namespace}.php` file into a sibling
47
+ * `lang/{locale}/{namespace}.json` file, one per PHP file, so each PHP file
48
+ * becomes its own i18next namespace instead of being merged with the others.
49
+ * A PHP file may sit at any depth under the locale directory --
50
+ * `lang/{locale}/{ns1}/{ns2}.php` and deeper -- and gets its sibling `.json`
51
+ * written at that same nested path; the resulting namespace name itself is
52
+ * inferred later, from the generated file's path, by `src/utils/recognizer.ts`.
53
+ *
54
+ * A source meant to hold hand-written JSON (e.g. a frontend-only catalog)
55
+ * should never also hold PHP files of the same name -- this directory is a
56
+ * PHP source, so its sibling `.json` is always regenerated, overwriting
57
+ * whatever was there before (including a leftover from a previous, uncleaned
58
+ * run). Keep hand-written translations in their own source directory.
59
+ *
60
+ * @param dirname
61
+ */ function parser(dirname) {
62
+ const sanitizedDirname = dirnameSanitize(dirname);
63
+ if (!fs.existsSync(sanitizedDirname)) {
64
+ return [];
65
+ }
66
+ return fs.readdirSync(sanitizedDirname).filter((locale)=>fs.statSync(path.join(sanitizedDirname, locale)).isDirectory()).sort().flatMap((locale)=>generateNamespaceFiles(path.join(sanitizedDirname, locale)));
67
+ }
68
+ /**
69
+ * Convert every `.php` file found anywhere under a locale directory into a
70
+ * sibling `.json` file at the same relative path, depth-first and
71
+ * alphabetically within each directory level.
72
+ *
73
+ * @param localeDirname
74
+ */ function generateNamespaceFiles(localeDirname) {
75
+ return walkPhpFiles(localeDirname).sort().map((phpPath)=>{
76
+ const content = fs.readFileSync(phpPath, 'utf-8');
77
+ const translations = convertToDottedKey(fromString(content));
78
+ const outputPath = phpPath.replace(/\.php$/, '.json');
79
+ fs.writeFileSync(outputPath, JSON.stringify(translations));
80
+ return {
81
+ basename: path.basename(outputPath),
82
+ path: outputPath
83
+ };
84
+ });
85
+ }
86
+ /**
87
+ * Every `.php` file under `dirname`, at any depth, as absolute paths.
88
+ *
89
+ * @param dirname
90
+ */ function walkPhpFiles(dirname) {
91
+ return fs.readdirSync(dirname, {
92
+ withFileTypes: true
93
+ }).flatMap((entry)=>{
94
+ const entryPath = path.join(dirname, entry.name);
95
+ if (entry.isDirectory()) return walkPhpFiles(entryPath);
96
+ return entry.isFile() && entry.name.endsWith('.php') ? [
97
+ entryPath
98
+ ] : [];
99
+ });
100
+ }
101
+ /**
102
+ * Flatten nested translations into dotted keys, e.g. `sub_level1.sub_level2.text`.
103
+ *
104
+ * @param source
105
+ * @param target
106
+ * @param keys
107
+ */ function convertToDottedKey(source, target = {}, keys = []) {
108
+ Object.entries(source).forEach(([key, value])=>{
109
+ const newPrefix = [
110
+ ...keys,
111
+ key
112
+ ];
113
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
114
+ convertToDottedKey(value, target, newPrefix);
115
+ } else {
116
+ target[newPrefix.join('.')] = value;
117
+ }
118
+ });
119
+ return target;
120
+ }
121
+
122
+ function isDirectory(dir) {
123
+ return fs.existsSync(dir) && fs.statSync(dir).isDirectory();
124
+ }
125
+ function countWildcardSegments(normalizedPattern) {
126
+ return normalizedPattern.split('/').filter((segment)=>segment === '*').length;
127
+ }
128
+ /**
129
+ * Every directory an ordered source `pattern` currently expands to, relative
130
+ * to `root`. A pattern with no `*` is a single literal directory (existing or
131
+ * not -- callers decide what an absent literal directory means). A pattern
132
+ * with exactly one `*` segment (e.g. `Modules/*\/lang`) is expanded against
133
+ * the filesystem: every existing subdirectory of the prefix before the `*`,
134
+ * with the suffix after it appended, kept only when the full path also
135
+ * exists, sorted. Two or more `*` segments throw -- unsupported.
136
+ *
137
+ * @param pattern
138
+ * @param root
139
+ */ function expandDirs(pattern, root) {
140
+ const normalized = pattern.replace(/[\\/]+/g, '/');
141
+ if (countWildcardSegments(normalized) > 1) {
142
+ throw new Error(`laravel-i18next: a source pattern may contain at most one "*" segment, got "${pattern}".`);
143
+ }
144
+ const segments = normalized.split('/').filter(Boolean);
145
+ const wildcardIndex = segments.indexOf('*');
146
+ if (wildcardIndex === -1) {
147
+ const dir = path.resolve(root, normalized);
148
+ return isDirectory(dir) ? [
149
+ dir
150
+ ] : [];
151
+ }
152
+ const prefix = path.resolve(root, segments.slice(0, wildcardIndex).join('/'));
153
+ const suffix = segments.slice(wildcardIndex + 1);
154
+ if (!isDirectory(prefix)) return [];
155
+ return fs.readdirSync(prefix, {
156
+ withFileTypes: true
157
+ }).filter((entry)=>entry.isDirectory()).map((entry)=>path.join(prefix, entry.name, ...suffix)).filter(isDirectory).sort();
158
+ }
159
+ /**
160
+ * The root-relative glob `import.meta.glob()` needs for a source pattern.
161
+ * Globs inside a virtual module have no containing file to be relative to,
162
+ * so this is always root-relative (a leading `/`). A `*` segment in the
163
+ * pattern passes through `path.resolve`/`path.relative`/`normalizePath`
164
+ * completely untouched -- none of them treat it specially -- which is why
165
+ * the emitted glob needs no separate wildcard handling: `Modules/*\/lang`
166
+ * becomes `/Modules/*\/lang/**\/*.json` and `import.meta.glob()` expands the
167
+ * `*` itself, natively, at both dev and build time.
168
+ *
169
+ * @param pattern
170
+ * @param root
171
+ */ function toGlob(pattern, root) {
172
+ const relative = normalizePath(path.relative(root, path.resolve(root, pattern.replace(/[\\/]+/g, '/'))));
173
+ return `/${relative}/**/*.json`;
174
+ }
175
+ /**
176
+ * Whether `file` sits under one of `dirnames` (any depth) -- used to decide
177
+ * whether a changed file belongs to any configured source at all.
178
+ *
179
+ * @param file
180
+ * @param dirnames
181
+ */ function isUnderAnySource(file, dirnames) {
182
+ const normalizedFile = normalizePath(file);
183
+ return dirnames.some((dir)=>normalizedFile.startsWith(`${normalizePath(dir)}/`));
184
+ }
185
+
186
+ /**
187
+ * Language files, one namespace per language file: loaded eagerly by server
188
+ * code (SSR), so it renders translated, and lazily by client code. The
189
+ * default export is an ARRAY, one entry per configured source in `sources`,
190
+ * highest priority first (see `LocaleFileSources` in `src/interfaces/options.ts`).
191
+ */ const FILES_MODULE_ID = 'virtual:laravel-i18next/files';
192
+ const RESOLVED_FILES_MODULE_ID = `\0${FILES_MODULE_ID}`;
193
+ /**
194
+ * Makes Laravel PHP translations available to i18next by generating a sibling
195
+ * `.json` file next to each `.php` file, across one or more ordered sources.
196
+ */ function i18n(config) {
197
+ var _a;
198
+ const logger = createLogger('info', {
199
+ prefix: '[laravel-i18next]'
200
+ });
201
+ const sourcePatterns = (_a = config === null || config === void 0 ? void 0 : config.sources) !== null && _a !== void 0 ? _a : [
202
+ 'lang'
203
+ ];
204
+ let root = process.cwd();
205
+ let generated = [];
206
+ let exitHandlersBound = false;
207
+ function clean() {
208
+ generated.forEach((file)=>fs.existsSync(file.path) && fs.unlinkSync(file.path));
209
+ generated = [];
210
+ }
211
+ /**
212
+ * (Re)generate every source's sibling `.json` files. Run once up front
213
+ * (`config()`) and again whenever a `.php` file changes (`hotUpdate` /
214
+ * `handleHotUpdate`).
215
+ */ function parseAll() {
216
+ let anyPhp = false;
217
+ generated = sourcePatterns.flatMap((pattern)=>{
218
+ const dirnames = expandDirs(pattern, root);
219
+ // A literal (wildcard-free) source that does not exist is very likely
220
+ // a setup mistake -- a wrong path, or (Laravel 9+) `php artisan
221
+ // lang:publish` was never run.
222
+ // A wildcard source matching nothing (no modules yet) is normal, so it
223
+ // stays silent.
224
+ if (dirnames.length === 0 && !pattern.includes('*')) {
225
+ logger.error(`Source "${pattern}" does not exist. Check the "sources" option (Laravel 9+ keeps language files in "lang", older versions in "resources/lang"), or on Laravel 9+ publish them with \`php artisan lang:publish\`.`, {
226
+ timestamp: true
227
+ });
228
+ logger.error('For more information please visit: https://laravel.com/docs/10.x/localization#publishing-the-language-files', {
229
+ timestamp: true
230
+ });
231
+ }
232
+ return dirnames.flatMap((dirname)=>{
233
+ if (locale.getPhpLocale(dirname).length > 0) anyPhp = true;
234
+ return parser(dirname);
235
+ });
236
+ });
237
+ if (!anyPhp) {
238
+ logger.info('No source contains PHP translation files.', {
239
+ timestamp: true
240
+ });
241
+ logger.info('For more information please visit: https://laravel.com/docs/10.x/localization#introduction', {
242
+ timestamp: true
243
+ });
244
+ }
245
+ return generated;
246
+ }
247
+ function isTranslationSource(file) {
248
+ return sourcePatterns.some((pattern)=>isUnderAnySource(file, expandDirs(pattern, root)));
249
+ }
250
+ return {
251
+ name: 'i18n',
252
+ enforce: 'post',
253
+ configResolved (resolvedConfig) {
254
+ root = resolvedConfig.root;
255
+ },
256
+ resolveId: {
257
+ order: 'pre',
258
+ handler (id) {
259
+ return id === FILES_MODULE_ID ? RESOLVED_FILES_MODULE_ID : undefined;
260
+ }
261
+ },
262
+ load (id, options) {
263
+ if (id !== RESOLVED_FILES_MODULE_ID) return undefined;
264
+ // Globs of virtual modules must be relative to the root. Recursive, so
265
+ // each also matches namespaces nested any number of levels deep.
266
+ const entries = sourcePatterns.map((pattern)=>JSON.stringify(toGlob(pattern, root))).map((pattern)=>(options === null || options === void 0 ? void 0 : options.ssr) ? `import.meta.glob(${pattern}, { eager: true })` : `import.meta.glob(${pattern})`).join(', ');
267
+ return `export default [${entries}];`;
268
+ },
269
+ config (userConfig) {
270
+ var _a;
271
+ // `config()` runs before `configResolved()`, so `root` is not yet the
272
+ // fully resolved one here -- close enough for source expansion, and
273
+ // `configResolved` overwrites it before `load()` ever runs.
274
+ root = path.resolve((_a = userConfig === null || userConfig === void 0 ? void 0 : userConfig.root) !== null && _a !== void 0 ? _a : process.cwd());
275
+ parseAll();
276
+ },
277
+ buildEnd: clean,
278
+ // `hotUpdate` (Vite 6+) is the modern hook; `handleHotUpdate` is kept for
279
+ // the `vite >= 5` peer range and is ignored by Vite whenever `hotUpdate`
280
+ // is also defined. Neither hook needs to invalidate the virtual module
281
+ // for an ADDED or DELETED json file, nor for a CHANGED one -- Vite's own
282
+ // `import.meta.glob` HMR already does that, virtual ids included. What
283
+ // is not free: a brand-new `.php` file has no sibling `.json` yet for
284
+ // that glob to notice, so it must be parsed here first.
285
+ hotUpdate (options) {
286
+ var _a;
287
+ if (!isTranslationSource(options.file)) return undefined;
288
+ if (options.file.endsWith('.php')) parseAll();
289
+ const filesModule = this.environment.moduleGraph.getModuleById(RESOLVED_FILES_MODULE_ID);
290
+ if (!filesModule) return undefined;
291
+ return [
292
+ ...(_a = this.environment.moduleGraph.getModulesByFile(options.file)) !== null && _a !== void 0 ? _a : [],
293
+ filesModule
294
+ ];
295
+ },
296
+ handleHotUpdate (ctx) {
297
+ if (isTranslationSource(ctx.file) && ctx.file.endsWith('.php')) parseAll();
298
+ },
299
+ configureServer () {
300
+ if (exitHandlersBound) return;
301
+ process.on('exit', clean);
302
+ process.on('SIGINT', process.exit);
303
+ process.on('SIGTERM', process.exit);
304
+ process.on('SIGHUP', process.exit);
305
+ exitHandlersBound = true;
306
+ }
307
+ };
308
+ }
309
+
310
+ export { FILES_MODULE_ID, i18n as default };
package/dist/vue.cjs ADDED
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ var I18NextVue = require('i18next-vue');
4
+ var createI18n = require('./shared/create-i18n-WsDK4Z8L.cjs');
5
+ require('i18next');
6
+
7
+ /**
8
+ * Vue plugin providing an i18next instance translating Laravel language files
9
+ * to `i18next-vue`'s `useTranslation()` composable and `$t`/`$i18next`.
10
+ *
11
+ * Every app instance owns its own i18next instance, so concurrent SSR requests
12
+ * do not share a language.
13
+ *
14
+ * ```ts
15
+ * import { createApp } from 'vue';
16
+ * import { laravelVueI18n } from '@genrwork/laravel-i18next/vue';
17
+ *
18
+ * createApp(App)
19
+ * .use(laravelVueI18n({ locale: 'uk', fallbackLocale: 'en', files: import.meta.glob('/lang/**\/*.json') }))
20
+ * .mount('#app');
21
+ * ```
22
+ */ function laravelVueI18n(options) {
23
+ return {
24
+ install (app) {
25
+ const i18next = createI18n.createI18n(options);
26
+ // Lives for the app's lifetime, so it is never unsubscribed.
27
+ createI18n.syncDocumentLang(i18next);
28
+ app.use(I18NextVue, {
29
+ i18next
30
+ });
31
+ }
32
+ };
33
+ }
34
+
35
+ Object.defineProperty(exports, "useTranslation", {
36
+ enumerable: true,
37
+ get: function () { return I18NextVue.useTranslation; }
38
+ });
39
+ exports.laravelVueI18n = laravelVueI18n;