Skip to content
This repository was archived by the owner on Jul 15, 2024. It is now read-only.
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"commander": "^7.0.0",
"dotenv": "^8.2.0",
"mkdirp": "^1.0.4",
"shelljs": "^0.8.4"
"shelljs": "^0.8.4",
"tempy": "^2.0.0"
},
"devDependencies": {
"@types/commander": "^2.12.2",
Expand Down
16 changes: 15 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import upload from "./upload";
import download from "./download";
import collect from "./collect";
import deleteBranch from "./delete-branch";
import validate from "./validate";
import config from "./config";

class BranchNameOption extends Option {
Expand All @@ -18,7 +19,7 @@ const main = async (argv: string[]) => {

program
.name("mollie-crowdin")
.usage("<upload | collect | download | delete-branch> [options]")
.usage("<upload | collect | download | delete-branch | validate> [options]")
.version(version);

program
Expand Down Expand Up @@ -75,6 +76,19 @@ const main = async (argv: string[]) => {
});
});

program
.command("validate <glob>")
.description(
"Validate if all translations keys have been translated for a set of languages"
)
.requiredOption(
"-l, --language <language...>",
'Language to validate, e.g. "nl".'
)
.action(async (glob: string, options) => {
await validate(glob, options);
});

program.parse(argv);
};

Expand Down
23 changes: 6 additions & 17 deletions src/collect.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
import { sync } from "mkdirp";
import shell from "shelljs";
import path from "path";
import config from "./config";
import log from "./utils/logging";
import formatjs from "./utils/formatjs";
import prettify from "./utils/prettify";

export default async (glob: string) => {
log.info("Extracting messages");
sync(config.INTL_DIR);
sync(config.INTL_DIR); // Create storage dir for transations if not exists

const cmd = [
path.join(process.cwd(), "node_modules", ".bin", "formatjs"),
"extract",
`"${glob}"`,
"--out-file",
config.TRANSLATIONS_FILE,
"--format",
"crowdin",
];

const { stderr } = shell.exec(cmd.join(" "));

if (stderr) {
log.error(stderr);
try {
await formatjs(glob, config.TRANSLATIONS_FILE);
} catch (error) {
log.error(`FormatJS was unable to scan the source files: ${error.message}`);
process.exit(1);
}

Expand Down
20 changes: 20 additions & 0 deletions src/utils/formatjs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import shell from "shelljs";
import path from "path";

export default async (glob: string, filename: string) => {
const cmd = [
path.join(process.cwd(), "node_modules", ".bin", "formatjs"),
"extract",
`"${glob}"`,
"--out-file",
`"${filename}"`,
"--format",
"crowdin",
];

const { stderr } = shell.exec(cmd.join(" "));

if (stderr) {
throw new Error(stderr);
}
};
104 changes: 104 additions & 0 deletions src/validate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Get all translation keys from project //collect
* Get all translations for languages
* Check if every language has the translation keys from project
*/
import config from "./config";
import formatjs from "./utils/formatjs";
import log from "./utils/logging";
import fs from "fs";
import path from "path";
import * as ts from "typescript";
import chalk from "chalk";

export default async (glob: string, options: { language: string[] }) => {
log.info("Collecting message keys from source files...");

/*
* Create a temporary file for FormatJS, this way we won't mess with the existing file which is under version control.
*/
const temporaryFileName = "messages.json.tmp";

try {
await formatjs(glob, temporaryFileName);
} catch (error) {
log.error(`FormatJS was unable to scan the source files: ${error.message}`);
process.exit(1);
}

const projectMessagesJson = fs.readFileSync(temporaryFileName, "utf-8");
fs.unlinkSync(temporaryFileName); // File is no longer needed.

let badApples = 0;

options.language.forEach((language: string): void => {
log.info(`Retrieving ${chalk.bold(language)} translations from project...`);

const projectMessages = JSON.parse(projectMessagesJson);
const messageKeys = Object.keys(projectMessages);

const projectTranslations = getTranslatedMessages(language);

// @TODO: as mvp just looping over keys to check if they exist
// have the correct placeholders

messageKeys.forEach(messageKey => {
if (!(messageKey in projectTranslations)) {
/*
* Message is not present in the translations file.
*/
log.error(
`Missing message ${chalk.bold(messageKey)} in language ${chalk.bold(
language
)}.`
);

badApples++;
} else if (
projectMessages[messageKey].message === projectTranslations[messageKey]
) {
/*
* Message is the same in the translations file as in the source file.
*/
log.error(
`Untranslated message ${chalk.bold(
messageKey
)} in language ${chalk.bold(language)}.`
);
badApples++;
}
});
});

log.info(`Found ${badApples} problems with the translations.`);

if (badApples > 0) {
process.exit(1);
}
};

/**
* Get the translations that are in the project and convert them to an object. Returns an object with the keys being
* the message keys and each value being an object with the keys message and optionally description.
* @param lang
*/
function getTranslatedMessages(lang: string): object {
/*
* Only Typescript is supported, sorry!
*/
const translationsFile = path.join(config.TRANSLATIONS_DIR, lang + ".ts");

const translationTypeScriptExport = fs.readFileSync(
translationsFile,
"utf-8"
);

const transpileOutput = ts.transpileModule(translationTypeScriptExport, {
compilerOptions: {
removeComments: true,
module: ts.ModuleKind.CommonJS,
},
});

return eval(transpileOutput.outputText);
}
Loading