Skip to content

Support for published package language vendor files #177

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

Merged
merged 6 commits into from
May 27, 2024
Merged
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
29 changes: 27 additions & 2 deletions src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ export const parseAll = (folderPath: string): ParsedLangFileInterface[] => {
})
}

// If data contains an object with folder name 'vendor'
const vendorIndex = data.findIndex(({ folder }) => folder === 'vendor');

if (vendorIndex !== -1) {
const vendorTranslations = data[vendorIndex].translations;
data.splice(vendorIndex, 1);

data.forEach(langFile =>
langFile.translations = mergeVendorTranslations(langFile.folder, langFile.translations, vendorTranslations));
}

return data
.filter(({ translations }) => {
return Object.keys(translations).length > 0
Expand All @@ -62,6 +73,20 @@ export const parseAll = (folderPath: string): ParsedLangFileInterface[] => {
})
}

function mergeVendorTranslations(folder: string, translations: any, vendorTranslations: any) {
// Filter the translations from the vendor file that match the current folder
const langTranslationsFromVendor = Object
.entries(vendorTranslations)
.filter(([key]) => key.includes(`.${folder}.`))
.reduce((acc, [key, value]) => ({
...acc,
[key.replace(`.${folder}.`, '::')]: value,
}), {});

// Merge the vendor translations that matched the folder with the current translations
return { ...translations, ...langTranslationsFromVendor };
}

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 @@ -155,8 +180,8 @@ export const readThroughDir = (dir) => {
return data
}

export const prepareExtendedParsedLangFiles = (langPaths: string[]) =>
langPaths.reduce((acc, langPath) => [...acc, ...parseAll(langPath)], new Array<ParsedLangFileInterface>())
export const prepareExtendedParsedLangFiles = (langPaths: string[]): ParsedLangFileInterface[] =>
langPaths.flatMap(langPath => parseAll(langPath));

export const generateFiles = (langPath: string, data: ParsedLangFileInterface[]): ParsedLangFileInterface[] => {
data = mergeData(data)
Expand Down
17 changes: 17 additions & 0 deletions test/fixtures/lang/vendor/package-example/en/messages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

return [
'welcome' => 'Welcome to the example package.',
'success' => 'The package did the task successfully.',
'foo' => [
'level1' => [
'level2' => 'package'
]
],
'arr' => ['foo', 'bar'],
'multiline' => 'Lorem ' .
'ipsum ' .
'dolor ' .
'sit ' .
'amet.',
];
17 changes: 17 additions & 0 deletions test/fixtures/lang/vendor/package-example/pt/messages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

return [
'welcome' => 'Bem-vindo ao exemplo do pacote.',
'success' => 'O pacote executou a tarefa com sucesso.',
'foo' => [
'level1' => [
'level2' => 'pacote'
]
],
'arr' => ['foo', 'bar'],
'multiline' => 'Lorem ' .
'ipsum ' .
'dolor ' .
'sit ' .
'amet.',
];
27 changes: 27 additions & 0 deletions test/folderIsolationUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import fs from 'fs'
import path from 'path'

export function isolateFolder(folderToIsolate: string, testName: string) {
const isolatedFolder = folderToIsolate + '_isolated_' + testName;
copyDirSync(folderToIsolate, isolatedFolder);

return isolatedFolder;
}

export function removeIsolatedFolder(isolatedFolder: string) {
fs.rmSync(isolatedFolder, { recursive: true, force: true })
}

function copyDirSync(source: string, destination: string) {
const exists = fs.existsSync(source);
const stats = exists && fs.statSync(source);
const isDirectory = exists && stats.isDirectory();
if (isDirectory) {
fs.mkdirSync(destination);
fs.readdirSync(source).forEach(childItemName => {
copyDirSync(path.join(source, childItemName), path.join(destination, childItemName));
});
} else {
fs.copyFileSync(source, destination);
}
}
58 changes: 43 additions & 15 deletions test/loader.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import fs from 'fs';
import { generateFiles, parseAll, parse, hasPhpTranslations, reset, prepareExtendedParsedLangFiles } from '../src/loader';
import { isolateFolder, removeIsolatedFolder } from './folderIsolationUtil'

beforeEach(() => reset(__dirname + '/fixtures/lang/'));
const isolatedFixtures = isolateFolder(__dirname + '/fixtures', 'loader');
afterAll(() => removeIsolatedFolder(isolatedFixtures));

beforeEach(() => reset(isolatedFixtures + '/lang/'));

it('creates a file for each lang', () => {
const langPath = __dirname + '/fixtures/lang/';
const langPath = isolatedFixtures + '/lang/';
const files = generateFiles(langPath, parseAll(langPath));

expect(files.length).toBe(3);
Expand All @@ -22,8 +26,32 @@ it('creates a file for each lang', () => {
expect(langPt['auth.foo.level1.level2']).toBe('barpt');
});

it('merges published package translations into each lang .json', () => {
const langPath = isolatedFixtures + '/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(langPath + files[0].name).toString());
expect(langEn['package-example::messages.welcome']).toBe('Welcome to the example package.');
expect(langEn['package-example::messages.foo.level1.level2']).toBe('package');
expect(langEn['package-example::messages.multiline']).toBe('Lorem ipsum dolor sit amet.');

const langFr = JSON.parse(fs.readFileSync(langPath + files[1].name).toString());
expect(langFr['package-example::messages.welcome']).toBeUndefined();
expect(langFr['package-example::messages.foo.level1.level2']).toBeUndefined();
expect(langFr['package-example::messages.multiline']).toBeUndefined();

const langPt = JSON.parse(fs.readFileSync(langPath + files[2].name).toString());
expect(langPt['package-example::messages.welcome']).toBe('Bem-vindo ao exemplo do pacote.');
expect(langPt['package-example::messages.foo.level1.level2']).toBe('pacote');
});

it('includes .php lang file in subdirectory in .json', () => {
const langPath = __dirname + '/fixtures/lang/';
const langPath = isolatedFixtures + '/lang/';
const files = generateFiles(langPath, parseAll(langPath));
const langEn = JSON.parse(fs.readFileSync(langPath + files[0].name).toString());

Expand All @@ -33,7 +61,7 @@ it('includes .php lang file in subdirectory in .json', () => {
});

it('includes .php lang file in nested subdirectory in .json', () => {
const langPath = __dirname + '/fixtures/lang/';
const langPath = isolatedFixtures + '/lang/';
const files = generateFiles(langPath, parseAll(langPath));
const langEn = JSON.parse(fs.readFileSync(langPath + files[0].name).toString())

Expand All @@ -42,9 +70,9 @@ it('includes .php lang file in nested subdirectory in .json', () => {
})

it('inclues additional lang paths to load from', () => {
const langPath = __dirname + '/fixtures/lang/';
const langPath = isolatedFixtures + '/lang/';
const additionalLangPaths = [
__dirname + '/fixtures/locales/'
isolatedFixtures + '/locales/'
];

const langPaths = prepareExtendedParsedLangFiles([
Expand All @@ -60,9 +88,9 @@ it('inclues additional lang paths to load from', () => {
});

it('overwrites translations from additional lang paths', () => {
const langPath = __dirname + '/fixtures/lang/';
const langPath = isolatedFixtures + '/lang/';
const additionalLangPaths = [
__dirname + '/fixtures/locales/'
isolatedFixtures + '/locales/'
];

const langPaths = prepareExtendedParsedLangFiles([
Expand All @@ -79,33 +107,33 @@ it('overwrites translations from additional lang paths', () => {
});

it('transforms .php lang to .json', () => {
const lang = parse(fs.readFileSync(__dirname + '/fixtures/lang/en/auth.php').toString());
const lang = parse(fs.readFileSync(isolatedFixtures + '/lang/en/auth.php').toString());

expect(lang['failed']).toBe('These credentials do not match our records.');
});

it('transform nested .php lang files to .json', () => {
const langPt = parse(fs.readFileSync(__dirname + '/fixtures/lang/pt/auth.php').toString());
const langPt = parse(fs.readFileSync(isolatedFixtures + '/lang/pt/auth.php').toString());
expect(langPt['foo.level1.level2']).toBe('barpt');

const langEn = parse(fs.readFileSync(__dirname + '/fixtures/lang/en/auth.php').toString());
const langEn = parse(fs.readFileSync(isolatedFixtures + '/lang/en/auth.php').toString());
expect(langEn['foo.level1.level2']).toBe('baren');
});

it('transforms simple index array to .json', () => {
const lang = parse(fs.readFileSync(__dirname + '/fixtures/lang/en/auth.php').toString());
const lang = parse(fs.readFileSync(isolatedFixtures + '/lang/en/auth.php').toString());
expect(lang['arr.0']).toBe('foo');
expect(lang['arr.1']).toBe('bar');
});

it('ignores empty `array` or `null` translations', () => {
const lang = parse(fs.readFileSync(__dirname + '/fixtures/lang/en/ignore.php').toString());
const lang = parse(fs.readFileSync(isolatedFixtures + '/lang/en/ignore.php').toString());

expect(lang['empty_array']).toBe(undefined);
expect(lang['null']).toBe(undefined);
});

it('checks if there is .php translations', () => {
expect(hasPhpTranslations(__dirname + '/fixtures/lang/')).toBe(true);
expect(hasPhpTranslations(__dirname + '/fixtures/wronglangfolder/')).toBe(false);
expect(hasPhpTranslations(isolatedFixtures + '/lang/')).toBe(true);
expect(hasPhpTranslations(isolatedFixtures + '/wronglangfolder/')).toBe(false);
});
Loading