Repository: xiCO2k/laravel-vue-i18n
Pull request #144 (2023-07-27 11:32:59)

Vite plugin options
With this PR, we can now pass additional paths from where we will load our translations, this is usefull when we are splitting our application's translations into different folders/paths, and/or we are using a package that helps us override default translations.


Patch:
diff --git a/README.md b/README.md
index 0c1ef15..1bdca9a 100644
--- a/README.md
+++ b/README.md
@@ -86,6 +86,35 @@ export default defineConfig({
 });
 ```
 
+#### Vite plugin options
+
+In addition to that, you can use this `Vite` plugin with additional paths to load from, this is usefull when you are using a package that let's you override your translations, or in case you are getting your application's lang files from different paths. 
+
+Note that if one key found in two paths, priority will be given to the last given path between these two (In this example translation key will be loaded from `public/locales`)
+
+```js
+// vite.config.js
+import i18n from 'laravel-vue-i18n/vite';
+
+export default defineConfig({
+    plugins: [
+        laravel([
+            'resources/css/app.css'
+            'resources/js/app.js',
+        ]),
+        vue(),
+
+        i18n({
+            // you can also change your langPath here
+            // langPath: 'locales' 
+            additionalLangPaths: [
+                'public/locales' // Load translations from this path too! 
+            ]
+        }),
+    ],
+});
+```
+
 > During the `npm run dev` execution time, the plugin will create some files like this `php_{lang}.json` on your lang folder.
 > And to avoid that to be commited to your code base, I suggest to your `.gitignore` this like:
 
diff --git a/package-lock.json b/package-lock.json
index 7d218d2..d6f8e8b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "laravel-vue-i18n",
-  "version": "2.5.0",
+  "version": "2.6.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "laravel-vue-i18n",
-      "version": "2.5.0",
+      "version": "2.6.0",
       "license": "MIT",
       "dependencies": {
         "php-parser": "3.1.3",
diff --git a/src/interfaces/plugin-options.ts b/src/interfaces/plugin-options.ts
index 9adfbd9..161f9ec 100644
--- a/src/interfaces/plugin-options.ts
+++ b/src/interfaces/plugin-options.ts
@@ -6,3 +6,8 @@ import { OptionsInterface } from './options'
 export interface PluginOptionsInterface extends OptionsInterface {
   shared?: boolean
 }
+
+export interface VitePluginOptionsInterface {
+  langPath?: string
+  additionalLangPaths?: string[]
+}
diff --git a/src/loader.ts b/src/loader.ts
index ed1921b..09e88d9 100644
--- a/src/loader.ts
+++ b/src/loader.ts
@@ -147,6 +147,9 @@ export const readThroughDir = (dir) => {
   return data
 }
 
+export const prepareExtendedParsedLangFiles = (langPaths: string[]) =>
+  langPaths.reduce((acc, langPath) => [...acc, ...parseAll(langPath)], new Array<ParsedLangFileInterface>())
+
 export const generateFiles = (langPath: string, data: ParsedLangFileInterface[]): ParsedLangFileInterface[] => {
   data = mergeData(data)
 
diff --git a/src/vite.ts b/src/vite.ts
index b7904b8..b99790b 100644
--- a/src/vite.ts
+++ b/src/vite.ts
@@ -1,12 +1,16 @@
 import path from 'path'
-import { existsSync, writeFileSync, unlinkSync, readdirSync, rmdirSync } from 'fs'
-import { parseAll, hasPhpTranslations, generateFiles } from './loader'
+import { existsSync, unlinkSync, readdirSync, rmdirSync } from 'fs'
+import { hasPhpTranslations, generateFiles, prepareExtendedParsedLangFiles } from './loader'
 import { ParsedLangFileInterface } from './interfaces/parsed-lang-file'
+import { VitePluginOptionsInterface } from './interfaces/plugin-options'
 import { Plugin } from 'vite'
 
-export default function i18n(langPath: string = 'lang'): Plugin {
+export default function i18n(options: string | VitePluginOptionsInterface = 'lang'): Plugin {
+  let langPath = typeof options === 'string' ? options : options.langPath ?? 'lang'
   langPath = langPath.replace(/[\\/]$/, '') + path.sep
 
+  const additionalLangPaths = typeof options === 'string' ? [] : options.additionalLangPaths ?? []
+
   const frameworkLangPath = 'vendor/laravel/framework/src/Illuminate/Translation/lang/'.replace('/', path.sep)
   let files: ParsedLangFileInterface[] = []
   let exitHandlersBound: boolean = false
@@ -40,11 +44,15 @@ export default function i18n(langPath: string = 'lang'): Plugin {
         return
       }
 
-      files = generateFiles(langPath, [...parseAll(frameworkLangPath), ...parseAll(langPath)])
+      const langPaths = prepareExtendedParsedLangFiles([frameworkLangPath, langPath, ...additionalLangPaths])
+
+      files = generateFiles(langPath, langPaths)
     },
     handleHotUpdate(ctx) {
       if (/lang\/.*\.php$/.test(ctx.file)) {
-        files = generateFiles(langPath, [...parseAll(frameworkLangPath), ...parseAll(langPath)])
+        const langPaths = prepareExtendedParsedLangFiles([frameworkLangPath, langPath, ...additionalLangPaths])
+
+        files = generateFiles(langPath, langPaths)
       }
     },
     configureServer(server) {


Test patch:
diff --git a/test/fixtures/locales/en/auth.php b/test/fixtures/locales/en/auth.php
new file mode 100644
index 0000000..f1d596a
--- /dev/null
+++ b/test/fixtures/locales/en/auth.php
@@ -0,0 +1,6 @@
+<?php 
+
+return [
+    'failed' => 'These credentials are incorrect.',
+    'throttle' => 'Too many login attempts. Please try again in :seconds seconds.'
+];
\ No newline at end of file
diff --git a/test/fixtures/locales/en/domain/user.php b/test/fixtures/locales/en/domain/user.php
new file mode 100644
index 0000000..e55c3ce
--- /dev/null
+++ b/test/fixtures/locales/en/domain/user.php
@@ -0,0 +1,5 @@
+<?php
+
+return [
+    'sub_dir_support_is_amazing' => 'Subdirectory override is amazing',
+];
diff --git a/test/loader.test.ts b/test/loader.test.ts
index aa45230..59a9eac 100644
--- a/test/loader.test.ts
+++ b/test/loader.test.ts
@@ -1,5 +1,5 @@
 import fs from 'fs';
-import { generateFiles, parseAll, parse, hasPhpTranslations, reset } from '../src/loader';
+import { generateFiles, parseAll, parse, hasPhpTranslations, reset, prepareExtendedParsedLangFiles } from '../src/loader';
 
 beforeEach(() => reset(__dirname + '/fixtures/lang/'));
 
@@ -41,6 +41,43 @@ it('includes .php lang file in nested subdirectory in .json', () => {
     expect(langEn['nested.cars.car.foo.level1.level2']).toBe('barpt');
 })
 
+it('inclues additional lang paths to load from', () => {
+    const langPath = __dirname + '/fixtures/lang/';
+    const additionalLangPaths = [
+        __dirname + '/fixtures/locales/'
+    ];
+
+    const langPaths = prepareExtendedParsedLangFiles([
+        langPath,
+        ...additionalLangPaths,
+    ]);
+
+    const files = generateFiles(langPath, langPaths);
+
+    const langEn = JSON.parse(fs.readFileSync(langPath + files[0].name).toString());
+
+    expect(langEn['auth.throttle']).toBe('Too many login attempts. Please try again in :seconds seconds.');
+});
+
+it('overwrites translations from additional lang paths', () => {
+    const langPath = __dirname + '/fixtures/lang/';
+    const additionalLangPaths = [
+        __dirname + '/fixtures/locales/'
+    ];
+
+    const langPaths = prepareExtendedParsedLangFiles([
+        langPath,
+        ...additionalLangPaths,
+    ]);
+
+    const files = generateFiles(langPath, langPaths);
+
+    const langEn = JSON.parse(fs.readFileSync(langPath + files[0].name).toString());
+
+    expect(langEn['auth.failed']).toBe('These credentials are incorrect.');
+    expect(langEn['domain.user.sub_dir_support_is_amazing']).toBe('Subdirectory override is amazing');
+});
+
 it('transforms .php lang to .json', () => {
     const lang = parse(fs.readFileSync(__dirname + '/fixtures/lang/en/auth.php').toString());