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

Feat/locale #65

Closed
wants to merge 8 commits into from
Closed
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
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"test": "nx test falso",
"migrate": "nx migrate latest",
"migrate:run": "nx migrate --run-migrations",
"m": "npx tsc tools/executors/manipulator/index && nx run falso:manipulator"
"m": "npx tsc tools/executors/manipulator/index && nx run falso:manipulator",
"translate": "npx tsc tools/executors/translate/index && nx run falso:translate"
},
"workspaces": [
"packages/falso",
Expand All @@ -39,6 +40,7 @@
"@nrwl/web": "13.4.5",
"@nrwl/workspace": "13.4.5",
"@phenomnomnominal/tsquery": "4.1.1",
"@rollup/plugin-json": "4.1.0",
"@types/estree": "0.0.50",
"@types/jest": "27.0.2",
"@types/node": "14.14.33",
Expand Down
4 changes: 4 additions & 0 deletions packages/falso/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,9 @@
"devDependencies": {
"@types/seedrandom": "3.0.1",
"@types/uuid": "8.3.4"
},
"exports": {
shhdharmen marked this conversation as resolved.
Show resolved Hide resolved
".": "./index.cjs.js",
"./i18n/*": "./i18n/*/index.cjs.js"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you verify it works with web bundlers such as Wepback, please? I' suggesting to build the library and npm link to dist

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked for webpack, it gave error for missing entry of . in exports, so added that and it worked fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this approach because you're referring only to cjs modules here, and web bundles can't tree-shake it.

Copy link
Contributor Author

@shhdharmen shhdharmen Jan 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other approach could be to have package.json for each language, and it contains main and module entries in it and remove exports from main package.json. Should I check that? We will need to take care that users still need to only install main package only.

Or can we use other builder than rollup? Like node? Which supports multiple entry points: https://nx.dev/node/build#additionalentrypoints. And it's built on top of webpack, which can help us with more configurations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need to ensure that users still need to install the main package only.

That's not an issue because it's tree-shakable. It'll not be bundled if they do not use the i18n functions.

There's a problem we did not think of - name collision. We should prefix the i18n function with the locale (e.g. ruRandFullName)

Let's try the other builder with the entry point support regarding the build.

Copy link
Contributor

@michaelxvoelker michaelxvoelker Jan 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the prefix in place, we can just export the i18n functions in the regular index.ts file, right? 🤔 No additional entry point is needed for packaging.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michaelxvoelker feel free to clone the repo or even the branch I created and try out what you mentioned. Let me know if it works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried node builder, but doesn't produce results the way we want. I will research more.

}
}
7 changes: 7 additions & 0 deletions packages/falso/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@
"options": {
"commitMessageFormat": "chore(${projectName}): release version ${version}"
}
},
"translate": {
"executor": "./tools/executors/translate:translate",
"options": {
"languages": ["es", "ru"],
"output": "packages/falso/src/i18n"
}
}
},
"tags": []
Expand Down
2 changes: 1 addition & 1 deletion packages/falso/src/lib/pronoun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ import { data } from './pronoun.json';
* pronoun()
*
*/
export function pronoun<Options extends FakeOptions>(options?: Options) {
export function randPronoun<Options extends FakeOptions>(options?: Options) {
return fake(data, options);
}
16 changes: 14 additions & 2 deletions post-build.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
const json = require('json-update');
const { execSync } = require('child_process');

json.update('dist/packages/falso/package.json', { main: './index.cjs.js' })
async function buildTranslationEntryPoints() {
execSync(`npx tsc --project packages/falso/tsconfig.lib.json`);
execSync(`npx rollup --config rollup.config.js`);
}

json
.update('dist/packages/falso/package.json', { main: './index.cjs.js' })
.then(() => {
console.log('Changed umd to cjs');
console.log('Changed falso/project.json main umd to cjs\n');
buildTranslationEntryPoints()
.then(() =>
console.log('Created cjs and esm js files for all translations.')
)
.catch((e) => console.error(e));
});
33 changes: 33 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import json from '@rollup/plugin-json';

export default [{
input: 'dist/out-tsc/packages/falso/src/i18n/es/index.js',
output: {
file: 'dist/packages/falso/i18n/es/index.cjs.js',
format: 'cjs',
},
plugins: [json()],
},
{
input: 'dist/out-tsc/packages/falso/src/i18n/es/index.js',
output: {
file: 'dist/packages/falso/i18n/es/index.esm.js',
format: 'esm',
},
plugins: [json()],
},{
input: 'dist/out-tsc/packages/falso/src/i18n/ru/index.js',
output: {
file: 'dist/packages/falso/i18n/ru/index.cjs.js',
format: 'cjs',
},
plugins: [json()],
},
{
input: 'dist/out-tsc/packages/falso/src/i18n/ru/index.js',
output: {
file: 'dist/packages/falso/i18n/ru/index.esm.js',
format: 'esm',
},
plugins: [json()],
},];
1 change: 1 addition & 0 deletions tools/executors/translate/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const UNICODE_REGEXP = /\\u([\d\w]{4})/gi;
export const COMMENT = `// THIS FILE IS AUTO-GENERATED THROUGH \`npm run translate\``;
export const TYPE_SCRIPT_FILE_EXTENSION = '.ts';
export const JSON_FILE_EXTENSION = '.json';
41 changes: 41 additions & 0 deletions tools/executors/translate/generate-rollup-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { readdirSync, writeFileSync } from 'fs';
import { EchoExecutorOptions } from './options';

export function generateRollupConfig(options: EchoExecutorOptions) {
const languageDirList = readdirSync(options.output);

let fileText = `import json from '@rollup/plugin-json';

export default `;

let configList = '[';

const rollupConfig = `{
input: 'dist/out-tsc/packages/falso/src/i18n/LANGUAGE/index.js',
output: {
file: 'dist/packages/falso/i18n/LANGUAGE/index.cjs.js',
format: 'cjs',
},
plugins: [json()],
},
{
input: 'dist/out-tsc/packages/falso/src/i18n/LANGUAGE/index.js',
output: {
file: 'dist/packages/falso/i18n/LANGUAGE/index.esm.js',
format: 'esm',
},
plugins: [json()],
},`;

languageDirList.forEach((language) => {
const config = rollupConfig.replace(new RegExp('LANGUAGE', 'g'), language);

configList += config;
});

configList += '];';

fileText += configList + '\n';

writeFileSync('rollup.config.js', fileText, { encoding: 'utf-8' });
}
101 changes: 33 additions & 68 deletions tools/executors/translate/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import { ExecutorContext } from '@nrwl/devkit';
import {
existsSync,
readdirSync,
readFileSync,
statSync,
unlinkSync,
} from 'fs';
import { join } from 'path';
import * as ts from 'typescript';
import { TYPE_SCRIPT_FILE_EXTENSION } from './constants';
import { generateStringLiteralsAndSourceFile } from './generate';
import { readdirSync, statSync } from 'fs';
import { basename, join } from 'path';
import { JSON_FILE_EXTENSION } from './constants';
import { generateRollupConfig } from './generate-rollup-config';
import { EchoExecutorOptions } from './options';
import {
manageLanguageIndexFiles,
Expand All @@ -31,77 +24,49 @@ export default async function translateExecutor(
context.workspace.projects[context.projectName].sourceRoot,
'lib'
);
options.path = options.path.map((p) => p + TYPE_SCRIPT_FILE_EXTENSION);
options.output = join(projectLibPath, options.output);
const jsonFilePaths = readdirSync(projectLibPath).filter((p) =>
NetanelBasal marked this conversation as resolved.
Show resolved Hide resolved
p.includes(JSON_FILE_EXTENSION)
);

manageLanguageIndexFiles(options);

console.info(`Will be translating ${options.path.length} files.\n`);
console.info(`Will be translating ${jsonFilePaths.length} files.\n`);

// un comment below once translation is ready
// for (let jsonFileName of jsonFilePaths) {

// for (let filePath of options.path) {
for (let filePath of readdirSync(projectLibPath)) {
await new Promise<void>((resolve, reject) =>
setTimeout(() => {
resolve();
}, 100)
);
const rootFilePath = join(projectLibPath, filePath);
const fileStats = statSync(rootFilePath);
// for testing, we are only translating 1st file, comment below once translation is ready
for (let jsonFileName of [jsonFilePaths[0]]) {
const TSFileName = basename(jsonFileName, '.json') + '.ts';
const rootJSONFilePath = join(projectLibPath, jsonFileName);
const fileStats = statSync(rootJSONFilePath);

const isFile = fileStats.isFile();
if (isFile) {
let { stringLiterals, sourceFile } =
generateStringLiteralsAndSourceFile(rootFilePath);

if (!stringLiterals?.length) {
console.info(`No string-literals found in file ${rootFilePath}\n`);

if (
existsSync(
rootFilePath.replace(TYPE_SCRIPT_FILE_EXTENSION, '.json')
)
) {
stringLiterals = JSON.parse(
readFileSync(
rootFilePath.replace(TYPE_SCRIPT_FILE_EXTENSION, '.json')
).toString()
).data;
}
}

if (stringLiterals?.length) {
// create i18n.json for existing files
await writeTranslation(options, stringLiterals, sourceFile, filePath);

// and then for languages
for (const language of options.languages) {
await writeTranslation(
options,
stringLiterals,
sourceFile,
filePath,
language
);
}
// and then for languages
for (const language of options.languages) {
await writeTranslation(
options,
projectLibPath,
jsonFileName,
TSFileName,
language
);
}
} else {
console.error(`File: ${rootFilePath} does not exist or is not file!`);
console.error(
`File: ${rootJSONFilePath} does not exist or is not file!`
);
}
}

// delete translations/index.ts file if present
// const translationIndex = join(options.output, `index.ts`);
// if (existsSync(translationIndex)) {
// unlinkSync(translationIndex);
// }

// write export statements everywhere
// TODO: Un-comment below
// writeExportStatements(options, translationIndex);
writeExportStatements(options);

// generate rollup config
generateRollupConfig(options);

// console.info(
// "Translation complete. Please make sure to export from package's index file"
// );
console.info('Translation complete. rollup.config.js generated');

return { success };
} catch (e) {
Expand Down
1 change: 0 additions & 1 deletion tools/executors/translate/options.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export interface EchoExecutorOptions {
path: string[];
languages: string[];
output: string;
}
9 changes: 1 addition & 8 deletions tools/executors/translate/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@
"type": "object",
"cli": "nx",
"properties": {
"path": {
"type": "array",
"description": "Files to translate",
"items": {
"type": "string"
}
},
"languages": {
"type": "array",
"description": "Languages",
Expand All @@ -23,5 +16,5 @@
"description": "The output dir path"
}
},
"required": ["path", "languages", "output"]
"required": ["languages", "output"]
}
62 changes: 59 additions & 3 deletions tools/executors/translate/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,70 @@ const translate = new v2.Translate({
keyFilename: './tools/executors/translate/files/google-cloud-secret.json',
});

export async function translateText(text: string[], target: string) {
export async function translateText(
text: string,
language: string
): Promise<string> {
// Translates the text into the target language. "text" can be a string for
// translating a single piece of text, or an array of strings for translating
// multiple texts.
// Un-comment below once you have google-cloud-secret.json file ready
// let [translations] = await translate.translate(text, target);

// Comment below once you have google-cloud-secret.json file ready
const translations = text.map((t) => 'Translated ' + t);
return translations;
const translation = language + '-' + text;
return translation;
}

export async function translateJSON(
jsonData: { data: any[] },
language: string
): Promise<{ data: any[] }> {
let updatedJSON: { data: any[] } = { data: [] };

const data = jsonData.data;

if (data) {
const updatedData = await translateElement(data, language);

updatedJSON.data = updatedData;

return updatedJSON;
}

throw new Error(`JSON's data must be present under root-level "data" key`);
}
function translateDataArray(data: any[], language: string): Promise<any[]> {
if (Array.isArray(data)) {
return Promise.all(
data.map(async (d): Promise<any> => {
return translateElement(d, language);
})
);
}
}
async function translateDataObject(data: any, language: string): Promise<any> {
const updatedData: any = {};

for (const key in data) {
const element = data[key];
let value = await translateElement(element, language);
updatedData[key] = value;
}

return updatedData;
}
async function translateElement(element: any, language: string) {
switch (typeof element) {
case 'string':
return await translateText(element, language);
case 'object':
if (Array.isArray(element)) {
return await translateDataArray(element, language);
} else {
return await translateDataObject(element, language);
}
default:
return element;
}
}
Loading