Repository: xiCO2k/laravel-vue-i18n
Pull request #108 (2023-03-03 14:24:27)

feat: Add support for laravel framework lang path


Patch:
diff --git a/src/interfaces/parsed-lang-file.ts b/src/interfaces/parsed-lang-file.ts
new file mode 100644
index 0000000..6e7521b
--- /dev/null
+++ b/src/interfaces/parsed-lang-file.ts
@@ -0,0 +1,7 @@
+/**
+ * The Interface that is responsible for a parsed lang file.
+ */
+export interface ParsedLangFileInterface {
+  name: string
+  translations: { [key: string]: string }
+}
diff --git a/src/loader.ts b/src/loader.ts
index ea33873..ed1921b 100644
--- a/src/loader.ts
+++ b/src/loader.ts
@@ -1,6 +1,7 @@
 import fs from 'fs'
 import path from 'path'
 import { Engine } from 'php-parser'
+import { ParsedLangFileInterface } from './interfaces/parsed-lang-file'
 
 export const hasPhpTranslations = (folderPath: string): boolean => {
   folderPath = folderPath.replace(/[\\/]$/, '') + path.sep
@@ -25,9 +26,13 @@ export const hasPhpTranslations = (folderPath: string): boolean => {
   return false
 }
 
-export const parseAll = (folderPath: string): { name: string; path: string }[] => {
+export const parseAll = (folderPath: string): ParsedLangFileInterface[] => {
   folderPath = folderPath.replace(/[\\/]$/, '') + path.sep
 
+  if (!fs.existsSync(folderPath)) {
+    return []
+  }
+
   const folders = fs
     .readdirSync(folderPath)
     .filter((file) => fs.statSync(folderPath + path.sep + file).isDirectory())
@@ -50,11 +55,10 @@ export const parseAll = (folderPath: string): { name: string; path: string }[] =
       return Object.keys(translations).length > 0
     })
     .map(({ folder, translations }) => {
-      const name = `php_${folder}.json`
-      const path = folderPath + name
-
-      fs.writeFileSync(path, JSON.stringify(translations))
-      return { name, path }
+      return {
+        name: `php_${folder}.json`,
+        translations
+      }
     })
 }
 
@@ -142,3 +146,36 @@ export const readThroughDir = (dir) => {
 
   return data
 }
+
+export const generateFiles = (langPath: string, data: ParsedLangFileInterface[]): ParsedLangFileInterface[] => {
+  data = mergeData(data)
+
+  if (!fs.existsSync(langPath)) {
+    fs.mkdirSync(langPath)
+  }
+
+  data.forEach(({ name, translations }) => {
+    fs.writeFileSync(langPath + name, JSON.stringify(translations))
+  })
+
+  return data
+}
+
+function mergeData(data: ParsedLangFileInterface[]): ParsedLangFileInterface[] {
+  const obj = {}
+
+  data.forEach(({ name, translations }) => {
+    if (!obj[name]) {
+      obj[name] = {}
+    }
+
+    obj[name] = { ...obj[name], ...translations }
+  })
+
+  const arr = []
+  Object.entries(obj).forEach(([name, translations]) => {
+    arr.push({ name, translations })
+  })
+
+  return arr
+}
diff --git a/src/mix.ts b/src/mix.ts
index c281ff7..eb9c57f 100644
--- a/src/mix.ts
+++ b/src/mix.ts
@@ -5,7 +5,7 @@ import mix from 'laravel-mix'
 import { Component } from 'laravel-mix/src/components/Component'
 import { EnvironmentPlugin, Configuration } from 'webpack'
 
-import { parseAll, hasPhpTranslations } from './loader'
+import { generateFiles, parseAll, hasPhpTranslations } from './loader'
 
 class BeforeBuildPlugin {
   callback: Function
@@ -23,10 +23,15 @@ mix.extend(
   'i18n',
   class extends Component {
     langPath: string
+    frameworkLangPath: string
     context: any
 
     register(langPath: string = 'lang'): void {
       this.langPath = this.context.paths.rootPath + path.sep + langPath
+      this.frameworkLangPath =
+        this.context.paths.rootPath +
+        path.sep +
+        'vendor/laravel/framework/src/Illuminate/Translation/lang/'.replace('/', path.sep)
     }
 
     webpackConfig(config: Configuration): void {
@@ -46,16 +51,20 @@ mix.extend(
 
       config.plugins.push(
         new BeforeBuildPlugin(() => {
-          files = parseAll(this.langPath)
+          files = generateFiles(this.langPath, [...parseAll(this.frameworkLangPath), ...parseAll(this.langPath)])
         })
       )
 
       this.context.listen('build', () => {
         files.forEach((file) => {
-          if (fs.existsSync(file.path)) {
-            fs.unlinkSync(file.path)
+          if (fs.existsSync(this.langPath + file.name)) {
+            fs.unlinkSync(this.langPath + file.name)
           }
         })
+
+        if (fs.existsSync(this.langPath) && fs.readdirSync(this.langPath).length < 1) {
+          fs.rmdirSync(this.langPath)
+        }
       })
     }
   }
diff --git a/src/vite.ts b/src/vite.ts
index 6b71eea..2a1fb81 100644
--- a/src/vite.ts
+++ b/src/vite.ts
@@ -1,25 +1,34 @@
-import { existsSync, unlinkSync } from 'fs'
-import { parseAll, hasPhpTranslations } from './loader'
+import path from 'path'
+import { existsSync, writeFileSync, unlinkSync, readdirSync, rmdirSync } from 'fs'
+import { parseAll, hasPhpTranslations, generateFiles } from './loader'
+import { ParsedLangFileInterface } from './interfaces/parsed-lang-file'
 
 export default function i18n(langPath: string = 'lang') {
-  let files: { name: string; path: string }[] = []
+  langPath = langPath.replace(/[\\/]$/, '') + path.sep
+
+  const frameworkLangPath = 'vendor/laravel/framework/src/Illuminate/Translation/lang/'.replace('/', path.sep)
+  let files: ParsedLangFileInterface[] = []
   let exitHandlersBound: boolean = false
 
   const clean = () => {
-    files.forEach((file) => unlinkSync(file.path))
+    files.forEach((file) => unlinkSync(langPath + file.name))
 
     files = []
+
+    if (existsSync(langPath) && readdirSync(langPath).length < 1) {
+      rmdirSync(langPath)
+    }
   }
 
   return {
     name: 'i18n',
     enforce: 'post',
     config(config) {
-      if (!hasPhpTranslations(langPath)) {
+      if (!hasPhpTranslations(frameworkLangPath) && !hasPhpTranslations(langPath)) {
         return
       }
 
-      files = parseAll(langPath)
+      files = generateFiles(langPath, [...parseAll(frameworkLangPath), ...parseAll(langPath)])
 
       /** @ts-ignore */
       process.env.VITE_LARAVEL_VUE_I18N_HAS_PHP = true
@@ -33,7 +42,7 @@ export default function i18n(langPath: string = 'lang') {
     buildEnd: clean,
     handleHotUpdate(ctx) {
       if (/lang\/.*\.php$/.test(ctx.file)) {
-        files = parseAll(langPath)
+        files = generateFiles(langPath, [...parseAll(frameworkLangPath), ...parseAll(langPath)])
       }
     },
     configureServer(server) {


Test patch:
diff --git a/test/loader.test.ts b/test/loader.test.ts
index 30c009e..aa45230 100644
--- a/test/loader.test.ts
+++ b/test/loader.test.ts
@@ -1,29 +1,31 @@
 import fs from 'fs';
-import { parseAll, parse, hasPhpTranslations, reset } from '../src/loader';
+import { generateFiles, parseAll, parse, hasPhpTranslations, reset } from '../src/loader';
 
 beforeEach(() => reset(__dirname + '/fixtures/lang/'));
 
 it('creates a file for each lang', () => {
-    const files = parseAll(__dirname + '/fixtures/lang/');
+    const langPath = __dirname + '/fixtures/lang/';
+    const files = generateFiles(langPath, parseAll(langPath));
 
     expect(files.length).toBe(3);
     expect(files[0].name).toBe('php_en.json');
     expect(files[1].name).toBe('php_fr.json');
     expect(files[2].name).toBe('php_pt.json');
 
-    const langEn = JSON.parse(fs.readFileSync(files[0].path).toString());
+    const langEn = JSON.parse(fs.readFileSync(langPath + files[0].name).toString());
     expect(langEn['auth.failed']).toBe('These credentials do not match our records.');
     expect(langEn['auth.foo.level1.level2']).toBe('baren');
     expect(langEn['auth.multiline']).toBe('Lorem ipsum dolor sit amet.');
 
-    const langPt = JSON.parse(fs.readFileSync(files[2].path).toString());
+    const langPt = JSON.parse(fs.readFileSync(langPath + files[2].name).toString());
     expect(langPt['auth.failed']).toBe('As credenciais indicadas não coincidem com as registadas no sistema.');
     expect(langPt['auth.foo.level1.level2']).toBe('barpt');
 });
 
 it('includes .php lang file in subdirectory in .json', () => {
-    const files = parseAll(__dirname + '/fixtures/lang/');
-    const langEn = JSON.parse(fs.readFileSync(files[0].path).toString());
+    const langPath = __dirname + '/fixtures/lang/';
+    const files = generateFiles(langPath, parseAll(langPath));
+    const langEn = JSON.parse(fs.readFileSync(langPath + files[0].name).toString());
 
     expect(langEn['domain.user.sub_dir_support_is_amazing']).toBe('Subdirectory support is amazing');
     expect(langEn['domain.car.is_electric']).toBe('Electric');
@@ -31,8 +33,9 @@ it('includes .php lang file in subdirectory in .json', () => {
 });
 
 it('includes .php lang file in nested subdirectory in .json', () => {
-    const files = parseAll(__dirname + '/fixtures/lang/');
-    const langEn = JSON.parse(fs.readFileSync(files[0].path).toString())
+    const langPath = __dirname + '/fixtures/lang/';
+    const files = generateFiles(langPath, parseAll(langPath));
+    const langEn = JSON.parse(fs.readFileSync(langPath + files[0].name).toString())
 
     expect(langEn['nested.cars.car.is_electric']).toBe('Electric');
     expect(langEn['nested.cars.car.foo.level1.level2']).toBe('barpt');
diff --git a/test/setup.ts b/test/setup.ts
index 3d616f9..2b4b1d0 100644
--- a/test/setup.ts
+++ b/test/setup.ts
@@ -1,6 +1,6 @@
 import { mount } from '@vue/test-utils'
 import { i18nVue } from '../src'
-import { parseAll } from '../src/loader'
+import { generateFiles, parseAll } from '../src/loader'
 
 global.mountPlugin = async (template = '<div />', lang = 'pt', fallbackLang = 'pt', fallbackMissingTranslations = false) => {
   const wrapper = mount({ template }, {
@@ -36,7 +36,8 @@ global.mountPluginWithRequire = async (template = '<div />', lang = 'pt', fallba
 }
 
 global.mixLoader = () => {
-  parseAll(__dirname + '/fixtures/lang/');
+  const langPath = __dirname + '/fixtures/lang/';
+  generateFiles(langPath, parseAll(langPath));
 
   process.env = Object.assign(process.env, {
     LARAVEL_VUE_I18N_HAS_PHP: 'true',