Skip to content

Commit

Permalink
Merge pull request #19 from derian-all-win-software/main
Browse files Browse the repository at this point in the history
Added the flexible language path in `TranslationConfiguration`
  • Loading branch information
immersedone authored Oct 20, 2024
2 parents 3966b05 + 45789a7 commit 4be2425
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 95 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,19 @@ console.log(translations);
...
```

## Custom language directory

By default, the plugin will look for language files in the `resources/lang` directory if the laravel version is minor than v9, otherwise it will look for language files in the `lang` directory. You can specify a custom directory by passing the `absoluteLanguageDirectory` option to the plugin.

```js
...
plugins: [
laravelTranslations({
absoluteLanguageDirectory: 'custom/path/to/lang',
}),
],
```


## Hot-Module Replacement (HMR)

Expand Down
46 changes: 23 additions & 23 deletions src/laravel.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
/**
* ##########################################
* # IMPORTS #
* ##########################################
*/
import path from 'path';
import fs from 'fs';
import { promises as fs } from 'fs';

/**
* Function: determineLaravelVersion()
* Description: Used to determine laravel version to determine
* the default path for language folder.
*
* @return Promise<Error|Number>
* @param composerPath string (default: 'composer.json') The path to composer.json file, in case it's not in root.
*
* @return Promise<Error|Number> The current Laravel version
*/
export const determineLaravelVersion = () => {
// # Read: Composer.json to determine file
return new Promise<number>(function (resolveVersion, rejectVersion) {
fs.readFile('composer.json', 'utf8', function (fileError: NodeJS.ErrnoException | null, fileData: string) {
// # Reject: Read File Error
if (fileError) {
rejectVersion(fileError);
}
export const determineLaravelVersion = async (composerPath: string = 'composer.json'): Promise<number> => {
try {
// Read Composer.json to determine the file
const fileData = await fs.readFile(composerPath, 'utf8');

// Extract Laravel Version
const composer = JSON.parse(fileData);
const laravelVersionRaw = composer.require['laravel/framework'];

// Extract Laravel Version using the first (0) index
const [laravelVersionString] = laravelVersionRaw.split('.');

// # Extract: Laravel Version
const composer = JSON.parse(fileData);
const laravelVersionRaw = composer.require['laravel/framework'];
const laravelVersion = parseInt(laravelVersionRaw.split('.')[0].replace(/\D/g, ''));
// Parse Laravel Version to Integer
const laravelVersion = parseInt(laravelVersionString.replace(/\D/g, ''));

// # Resolve: Laravel Version
resolveVersion(laravelVersion);
});
});
// Return Laravel Version (e.g. 11)
return laravelVersion;
} catch (exception) {
// Throw exception if composer.json file is not found
throw exception;
}
};

/**
Expand Down
157 changes: 87 additions & 70 deletions src/loader.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,60 @@
/**
* ##########################################
* # IMPORTS #
* ##########################################
*/
import { globSync } from 'glob';
import path from 'path';
// @ts-ignore - No types from JS package
import phpArrayReader from 'php-array-reader';
import { globSync } from 'glob';
import path from 'path';
import { TranslationConfiguration } from '../types';
import { mergeDeep } from './utils/mergeDeep';
import { TranslationConfiguration } from '../types/index.js';

/**
* Get the glob pattern based on the configuration
*
* @param shouldIncludeJson - Should include JSON files
* @returns string - The glob pattern
*/
const globPattern = (shouldIncludeJson: boolean): string => (shouldIncludeJson ? '**/*.{json,php}' : '**/*.php');

/**
* Configure the namespace for the path split
*
* @param pathSplit - The path split
* @param namespace - The namespace
* @returns string[] - The path split with the namespace
*/
const configureNamespaceIfNeeded = (pathSplit: string[], namespace: string): string[] => {
if (namespace && namespace.length > 0) {
// Append configured namespace
pathSplit.splice(1, 0, namespace);
}
return pathSplit;
};

/**
* Get the translation content by file extension
*
* @param fileExtension - The file extension
* @param file - The file path
* @returns Promise<any> - The translation content
*/
const translationContentByFileExtension = async (fileExtension: string, file: string): Promise<string> => {
if (fileExtension === '.php') {
return phpArrayReader.fromFile(file);
}

const fullPath = `${process.cwd()}/${file}`;
return await import(fullPath);
};

/**
* Generate the nested object structure
*
* @param pathSplit - The path split
* @param all - The all object
* @returns - The nested object structure
*/
const generateNestedObjectStructure = (pathSplit: string[], all: any): object =>
pathSplit.reverse().reduce((all, item) => ({ [item]: all }), all);

/**
* Function: buildTranslations()
* Description: Main function that fetches all of the Laravel translations
Expand All @@ -18,73 +64,44 @@ import { TranslationConfiguration } from '../types/index.js';
* @param pluginConfiguration - Plugin configurations
* @returns translations - Object/JSON version of Laravel Translations
*/
export const buildTranslations = async (absLangPath: string, pluginConfiguration: TranslationConfiguration) => {
// # Define: Translation Object
let translations = {};

// # Define: Glob Regex
const globRegex = pluginConfiguration.includeJson ? '**/*.{json,php}' : '**/*.php';

// # Recursively: Fetch filenames as an array
const files = globSync(path.join(absLangPath, globRegex), { windowsPathsNoEscape: true });

// # Loop: Through each of the files and create object
for (const file of files) {
// # Define: File information
const fileRaw = file.replace(absLangPath + path.sep, '');
const fileExt = path.extname(fileRaw);
const pathSplit = fileRaw.replace(fileExt, '').split(path.sep);

// # Import/Parse: The .PHP/.JSON language file.
// # Pre-Define: Initial Value for reducer
const all = fileExt == '.php' ? phpArrayReader.fromFile(file) : await import(file);

// # Configure: Namespaces
if (typeof pluginConfiguration.namespace == 'string' && pluginConfiguration.namespace.length > 0) {
// # Append configured namespace
pathSplit.splice(1, 0, pluginConfiguration.namespace);
}

// # Generate: Nested Object from array
const currentTranslationStructure = pathSplit.reverse().reduce((all, item) => ({ [item]: all }), all);

// # Merge-Deep: Existing translations with current translations
translations = mergeDeep(translations, currentTranslationStructure);
}
export const buildTranslations = async (
absLangPath: string,
pluginConfiguration: TranslationConfiguration
): Promise<object> => {
// Define the language directory
const langDir = pluginConfiguration.absoluteLanguageDirectory || absLangPath;

// # Return: Imported Laravel translations as JSON
return translations;
};
// Define the glob pattern
const globRegex = globPattern(pluginConfiguration.includeJson || false);

/**
* Function: mergeDeep()
* Description: Method used to deeply merge objects that are nested.
*
* @param target - Main Object to merge into
* @param source - Object 2 containing data to merge into main object
* @returns target
*/
// @ts-ignore - Unknown object definitions
const mergeDeep = (target, source) => {
// @ts-ignore - Unknown object definitions
const isObject = (obj) => obj && typeof obj === 'object';
// Fetch filenames as an array
const files = globSync(path.join(langDir, globRegex), { windowsPathsNoEscape: true });

if (!isObject(target) || !isObject(source)) {
return source;
}
// Define initial translations
const initialTranslations = Promise.resolve({});

// Create translations object
const translations = await files.reduce(async (accumulator, file) => {
const { sep: pathSeparator } = path;

Object.keys(source).forEach((key) => {
const targetValue = target[key];
const sourceValue = source[key];
// Wait for the accumulator to resolve
const translations = await accumulator;

if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
target[key] = targetValue.concat(sourceValue);
} else if (isObject(targetValue) && isObject(sourceValue)) {
target[key] = mergeDeep(Object.assign({}, targetValue), sourceValue);
} else {
target[key] = sourceValue;
}
});
// Extract the file path
const fileRaw = file.replace(langDir + pathSeparator, '');

return target;
// Extract the file extension
const fileExtension = path.extname(fileRaw);

// Extract the path split
const pathSplit = fileRaw.replace(fileExtension, '').split(pathSeparator);

const translationContent = await translationContentByFileExtension(fileExtension, file);
const namespacePath = configureNamespaceIfNeeded(pathSplit, pluginConfiguration.namespace || '');
const currentTranslationStructure = generateNestedObjectStructure(namespacePath, translationContent);

return mergeDeep(translations, currentTranslationStructure);
}, initialTranslations);

return translations;
};
32 changes: 32 additions & 0 deletions src/utils/mergeDeep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Function: mergeDeep()
* Description: Method used to deeply merge objects that are nested.
*
* @param target - Main Object to merge into
* @param source - Object 2 containing data to merge into main object
* @returns target
*/
// @ts-ignore - Unknown object definitions
export const mergeDeep = (target, source) => {
// @ts-ignore - Unknown object definitions
const isObject = (obj) => obj && typeof obj === 'object';

if (!isObject(target) || !isObject(source)) {
return source;
}

Object.keys(source).forEach((key) => {
const targetValue = target[key];
const sourceValue = source[key];

if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
target[key] = targetValue.concat(sourceValue);
} else if (isObject(targetValue) && isObject(sourceValue)) {
target[key] = mergeDeep(Object.assign({}, targetValue), sourceValue);
} else {
target[key] = sourceValue;
}
});

return target;
};
5 changes: 3 additions & 2 deletions src/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ export default async function laravelTranslations(pluginConfiguration: Translati
// # Define: Default Configurations
const defaultConfigurations: TranslationConfiguration = {
namespace: false,
includeJson: false
includeJson: false,
absoluteLanguageDirectory: null
};

// # Retrieve: Laravel Version
const laravelVersion = await determineLaravelVersion();

// # Retrieve: Laravel Path (Absolute)
const absPathForLangDir = getLangDir(laravelVersion);
const absPathForLangDir = pluginConfiguration.absoluteLanguageDirectory || getLangDir(laravelVersion);

return {
// # Define: Plugin Name for Vite
Expand Down
1 change: 1 addition & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export declare interface TranslationConfiguration {
namespace?: string | false;
// # [TO_DO]: Implement JSON files
includeJson?: boolean;
absoluteLanguageDirectory?: string | null; // Optional param to override default langDir if needed
}

0 comments on commit 4be2425

Please sign in to comment.