Skip to content

Commit

Permalink
Support for published package language vendor files (#177)
Browse files Browse the repository at this point in the history
* Support for published package language vendor files

* Added published package lang examples

* Added test for php_vendor.json merge into other langs .json

* Refactored merge logic to parseAll from prepareExtendedParsedLangFiles

* Adjusted loader test to correspond with parseAll function's output

* Isolate fixture folder in loader.test
  • Loading branch information
Haruki1707 authored May 27, 2024
1 parent 66a2e6d commit 565faad
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 17 deletions.
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);
});

0 comments on commit 565faad

Please sign in to comment.