Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for auto-loading vendor packages language paths #181

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}),
],
});
Expand Down
7 changes: 7 additions & 0 deletions src/interfaces/package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* This interface is used to define the structure of a package.
*/
interface Package {
name: string;
langPath: string;
}
1 change: 1 addition & 0 deletions src/interfaces/plugin-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export interface PluginOptionsInterface extends OptionsInterface {
export interface VitePluginOptionsInterface {
langPath?: string
additionalLangPaths?: string[]
loadPackagesLangPaths?: boolean
}
37 changes: 34 additions & 3 deletions src/loader.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -197,6 +208,26 @@ 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 pkgPath = `${vendorFolder}/${vendor.name}/${pkg.name}`;

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);
});
};

function mergeData(data: ParsedLangFileInterface[]): ParsedLangFileInterface[] {
const obj = {}

Expand Down
9 changes: 6 additions & 3 deletions src/vite.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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[] = []
Expand Down Expand Up @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/lang/vendor/package-example/en/messages.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

return [
'welcome' => '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.',
];
27 changes: 26 additions & 1 deletion test/loader.test.ts
Original file line number Diff line number Diff line change
@@ -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');
Expand Down Expand Up @@ -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());

Expand Down
Loading