From f95bdceb9d34dfb435919e3abbe862b8f152d93e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20G=C3=B3mez?= <72423267+Haruki1707@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:41:43 -0600 Subject: [PATCH 1/5] Added support for extra packagesLangPaths --- src/interfaces/package.ts | 7 +++++++ src/loader.ts | 31 ++++++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 src/interfaces/package.ts diff --git a/src/interfaces/package.ts b/src/interfaces/package.ts new file mode 100644 index 0000000..166c9e4 --- /dev/null +++ b/src/interfaces/package.ts @@ -0,0 +1,7 @@ +/** + * This interface is used to define the structure of a package. + */ +interface Package { + name: string; + langPath: string; +} \ No newline at end of file diff --git a/src/loader.ts b/src/loader.ts index b0bb14a..3d63c56 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -1,4 +1,4 @@ -import fs from 'fs' +import fs, { existsSync, readdirSync } from 'fs' import path from 'path' import { Engine } from 'php-parser' import { ParsedLangFileInterface } from './interfaces/parsed-lang-file' @@ -87,6 +87,14 @@ function mergeVendorTranslations(folder: string, translations: any, vendorTransl return { ...translations, ...langTranslationsFromVendor }; } +export const parsePackage = (langPath: string, packageName: string) => { + return parseAll(langPath).map(langFile => { + const reducedTranslations = Object.entries(langFile.translations) + .reduce((acc, [key, value]) => ({...acc, [`${packageName}::${key}`]: value}), {}); + return {...langFile, translations: reducedTranslations}; + }); +} + export const parse = (content: string) => { const arr = new Engine({}).parseCode(content, 'lang').children.filter((child) => child.kind === 'return')[0] as any @@ -180,8 +188,11 @@ export const readThroughDir = (dir) => { return data } -export const prepareExtendedParsedLangFiles = (langPaths: string[]): ParsedLangFileInterface[] => - langPaths.flatMap(langPath => parseAll(langPath)); +export const prepareExtendedParsedLangFiles = (langPaths: string[], packages?: Package[]): ParsedLangFileInterface[] => + [ + ...(packages || []).flatMap(pkg => parsePackage(pkg.langPath, pkg.name)), + ...langPaths.flatMap(langPath => parseAll(langPath)), + ]; export const generateFiles = (langPath: string, data: ParsedLangFileInterface[]): ParsedLangFileInterface[] => { data = mergeData(data) @@ -197,6 +208,20 @@ export const generateFiles = (langPath: string, data: ParsedLangFileInterface[]) return data } +export const getPackagesLangPaths = (vendorFolder = 'vendor'): Package[] => { + const vendors = readdirSync(vendorFolder, {withFileTypes: true}).filter(dir => dir.isDirectory()); + + return vendors.flatMap(vendor => { + const packages = readdirSync(`${vendorFolder}/${vendor.name}`, {withFileTypes: true}).filter(dir => dir.isDirectory()); + + return packages.map(pkg => { + const langPath = `${vendorFolder}/${vendor.name}/${pkg.name}/lang`; + + return existsSync(langPath) ? {name: pkg.name, langPath: langPath} : null; + }).filter(Boolean); + }); +}; + function mergeData(data: ParsedLangFileInterface[]): ParsedLangFileInterface[] { const obj = {} From f1499764967d227e35f4cccd27371b66ff7cc93e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20G=C3=B3mez?= <72423267+Haruki1707@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:42:26 -0600 Subject: [PATCH 2/5] loadPackagesLangPaths vite plugin option to load packages --- src/interfaces/plugin-options.ts | 1 + src/vite.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/interfaces/plugin-options.ts b/src/interfaces/plugin-options.ts index 161f9ec..f763054 100644 --- a/src/interfaces/plugin-options.ts +++ b/src/interfaces/plugin-options.ts @@ -10,4 +10,5 @@ export interface PluginOptionsInterface extends OptionsInterface { export interface VitePluginOptionsInterface { langPath?: string additionalLangPaths?: string[] + loadPackagesLangPaths?: boolean } diff --git a/src/vite.ts b/src/vite.ts index 0e5c9eb..e5d19ee 100644 --- a/src/vite.ts +++ b/src/vite.ts @@ -1,6 +1,6 @@ import path from 'path' import { existsSync, unlinkSync, readdirSync, rmdirSync } from 'fs' -import { hasPhpTranslations, generateFiles, prepareExtendedParsedLangFiles } from './loader' +import { hasPhpTranslations, generateFiles, prepareExtendedParsedLangFiles, getPackagesLangPaths } from './loader' import { ParsedLangFileInterface } from './interfaces/parsed-lang-file' import { VitePluginOptionsInterface } from './interfaces/plugin-options' import { Plugin } from 'vite' @@ -10,6 +10,7 @@ export default function i18n(options: string | VitePluginOptionsInterface = 'lan langPath = langPath.replace(/[\\/]$/, '') + path.sep const additionalLangPaths = typeof options === 'string' ? [] : options.additionalLangPaths ?? [] + const loadPackagesLangPaths = typeof options === 'string' ? false : options.loadPackagesLangPaths ?? false const frameworkLangPath = 'vendor/laravel/framework/src/Illuminate/Translation/lang/'.replace('/', path.sep) let files: ParsedLangFileInterface[] = [] @@ -49,13 +50,15 @@ export default function i18n(options: string | VitePluginOptionsInterface = 'lan return } - const langPaths = prepareExtendedParsedLangFiles([frameworkLangPath, langPath, ...additionalLangPaths]) + const packagesLangPaths = loadPackagesLangPaths ? getPackagesLangPaths() : null; + const langPaths = prepareExtendedParsedLangFiles([frameworkLangPath, langPath, ...additionalLangPaths], packagesLangPaths) files = generateFiles(langPath, langPaths) }, handleHotUpdate(ctx) { if (/lang\/.*\.php$/.test(ctx.file)) { - const langPaths = prepareExtendedParsedLangFiles([frameworkLangPath, langPath, ...additionalLangPaths]) + const packagesLangPaths = loadPackagesLangPaths ? getPackagesLangPaths() : null; + const langPaths = prepareExtendedParsedLangFiles([frameworkLangPath, langPath, ...additionalLangPaths], packagesLangPaths) files = generateFiles(langPath, langPaths) } From 87caefdb9192c5bc40980f74329b7955e4ae8afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20G=C3=B3mez?= <72423267+Haruki1707@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:45:26 -0600 Subject: [PATCH 3/5] Tests & Fixtures for extra packagesLangPaths --- .../vendor/package-example/en/messages.php | 1 + .../package-example/lang/en/messages.php | 8 ++++++ test/loader.test.ts | 27 ++++++++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/vendor/package-creator/package-example/lang/en/messages.php diff --git a/test/fixtures/lang/vendor/package-example/en/messages.php b/test/fixtures/lang/vendor/package-example/en/messages.php index 4cfb5b2..948042e 100644 --- a/test/fixtures/lang/vendor/package-example/en/messages.php +++ b/test/fixtures/lang/vendor/package-example/en/messages.php @@ -3,6 +3,7 @@ return [ 'welcome' => 'Welcome to the example package.', 'success' => 'The package did the task successfully.', + 'overwrite_example' => 'This is the message being overwritten.', 'foo' => [ 'level1' => [ 'level2' => 'package' diff --git a/test/fixtures/vendor/package-creator/package-example/lang/en/messages.php b/test/fixtures/vendor/package-creator/package-example/lang/en/messages.php new file mode 100644 index 0000000..8636726 --- /dev/null +++ b/test/fixtures/vendor/package-creator/package-example/lang/en/messages.php @@ -0,0 +1,8 @@ + 'Welcome to the example package.', + 'success' => 'We executed the task successfully.', + 'error' => 'An error occurred while executing the task.', + 'overwrite_example' => 'This is an example of the original message without overwriting.', +]; diff --git a/test/loader.test.ts b/test/loader.test.ts index f8fddbf..78c39e3 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, prepareExtendedParsedLangFiles } from '../src/loader'; +import { generateFiles, parseAll, parse, hasPhpTranslations, reset, prepareExtendedParsedLangFiles, getPackagesLangPaths } from '../src/loader' import { isolateFolder, removeIsolatedFolder } from './folderIsolationUtil' const isolatedFixtures = isolateFolder(__dirname + '/fixtures', 'loader'); @@ -106,6 +106,31 @@ it('overwrites translations from additional lang paths', () => { expect(langEn['domain.user.sub_dir_support_is_amazing']).toBe('Subdirectory override is amazing'); }); +it('includes vendor package translations into each lang .json', () => { + const langPath = isolatedFixtures + '/lang/'; + const vendorPath = isolatedFixtures + '/vendor/'; + + const files = generateFiles(langPath, + prepareExtendedParsedLangFiles([langPath], getPackagesLangPaths(vendorPath)) + ); + + const langEn = JSON.parse(fs.readFileSync(langPath + files[0].name).toString()); + expect(langEn['package-example::messages.error']).toBe('An error occurred while executing the task.'); +}); + +it('overwrites vendor package translations into each lang .json', () => { + const langPath = isolatedFixtures + '/lang/'; + const vendorPath = isolatedFixtures + '/vendor/'; + + const files = generateFiles(langPath, + prepareExtendedParsedLangFiles([langPath], getPackagesLangPaths(vendorPath)) + ); + + const langEn = JSON.parse(fs.readFileSync(langPath + files[0].name).toString()); + expect(langEn['package-example::messages.success']).toBe('The package did the task successfully.'); + expect(langEn['package-example::messages.overwrite_example']).toBe('This is the message being overwritten.'); +}); + it('transforms .php lang to .json', () => { const lang = parse(fs.readFileSync(isolatedFixtures + '/lang/en/auth.php').toString()); From 35d319f31be91dbc5751827b2847d9774a8678a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20G=C3=B3mez?= <72423267+Haruki1707@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:46:06 -0600 Subject: [PATCH 4/5] Update README.md to contain 'loadPackagesLangPaths' option --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f53ef74..224ff2e 100644 --- a/README.md +++ b/README.md @@ -106,10 +106,13 @@ export default defineConfig({ i18n({ // you can also change your langPath here - // langPath: 'locales' + // langPath: 'locales', additionalLangPaths: [ 'public/locales' // Load translations from this path too! - ] + ], + // if you want to load extra packages lang files, you can set this to true + // it will load them just as laravel does: PackageName::messages.example + loadPackagesLangPaths: true // Load all packages lang files, default is false }), ], }); From 97346aad8b8fd5d7b45d62bf37478c623a6bdb8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20G=C3=B3mez?= <72423267+Haruki1707@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:44:36 -0600 Subject: [PATCH 5/5] Fix default lang path as Laravel use it --- src/loader.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/loader.ts b/src/loader.ts index 3d63c56..b5518d2 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -215,9 +215,15 @@ export const getPackagesLangPaths = (vendorFolder = 'vendor'): Package[] => { const packages = readdirSync(`${vendorFolder}/${vendor.name}`, {withFileTypes: true}).filter(dir => dir.isDirectory()); return packages.map(pkg => { - const langPath = `${vendorFolder}/${vendor.name}/${pkg.name}/lang`; + const pkgPath = `${vendorFolder}/${vendor.name}/${pkg.name}`; - return existsSync(langPath) ? {name: pkg.name, langPath: langPath} : null; + if (existsSync(`${pkgPath}/resources/lang`)) { + return {name: pkg.name, langPath: `${pkgPath}/resources/lang`}; + } else if (existsSync(`${pkgPath}/lang`)) { + return {name: pkg.name, langPath: `${pkgPath}/lang`}; + } + + return null; }).filter(Boolean); }); };