Skip to content

Commit

Permalink
Merge pull request #7 from favoloso/feat-translations
Browse files Browse the repository at this point in the history
✨ Add translations support for headings and rules
  • Loading branch information
leonardfactory authored Mar 2, 2019
2 parents 189cd2a + bd219ad commit 0dba422
Show file tree
Hide file tree
Showing 23 changed files with 199 additions and 32 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ The package works as-is, but its behaviour may be customized with the following

> 4. Major version zero (0.y.z) is for initial development. Anything may change at any time. The public API should not be considered stable.
- **`language`** (default: `en`)

Allows to translate commits group heading in `CHANGELOG.md` (i.e. `🐛 Bug Fixes`) and in linter messages.
Languages available: `en`, `it`

### Example config in package.json

```json
Expand Down
19 changes: 19 additions & 0 deletions __tests__/emoji-preset.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,23 @@ describe("emoji preset", () => {
});
});
});

describe("heading translations", () => {
it("should allow to translate headings", () => {
gitCommit("🚦 test");
gitCommit("✨ feat");
gitCommit("📦 build");
gitCommit("⚡️ perfo");
gitCommit("🛠 improvement");
jest.setMock("../src/config/config", {
language: "it"
});
return getChangelog().then(changelog => {
expect(changelog).toContainString("### ⚡️ Performance");
expect(changelog).toContainString("### ✨ Nuove Funzionalità");
expect(changelog).toContainString("### 🛠 Migliorie");
expect(changelog).not.toContainString("### ✨ Features");
});
});
});
});
42 changes: 42 additions & 0 deletions __tests__/translation.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
function translator() {
return require("../src/translation/translator")();
}

function setConfig(other) {
const config = require("../src/config/config-default");
jest.setMock("../src/config/config", {
...config,
...other
});
}

describe("translator", () => {
beforeEach(() => {
jest.resetModules();
});

it("should throw if language is not found", () => {
setConfig({ language: "xyz-hello" });
expect(() => translator().translateRule("abc")).toThrowError(
/^Language "xyz-hello" is not available\./
);
});

describe("linter rules", () => {
it("should translate correctly in english", () => {
setConfig({ language: "en" });
expect(
translator().translateRule("body-leading-blank")
).toMatchInlineSnapshot(`"Body should begin with a leading blank line."`);
});

it("should translate in another language", () => {
setConfig({ language: "it" });
expect(
translator().translateRule("body-leading-blank")
).toMatchInlineSnapshot(
`"Il <body> del commit deve iniziare con una nuova linea (\\\\n)"`
);
});
});
});
2 changes: 1 addition & 1 deletion src/config/config-default.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
fixAliasedEmoji: false,
language: "en",
showEmojiPerCommit: false,
minorForBreakingInDevelopment: true,
emojis: {},
Expand Down
6 changes: 3 additions & 3 deletions src/conventional-changelog/factory/commit-groups-sort.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
const emojiRegex = require("emoji-regex")();
const emoji = require("../../emoji/emoji");
const translator = require("../../translation/translator")();

/**
* Order groups by emoji configuration.
*/
module.exports = function commitGroupsSort(commitGroup, otherCommitGroup) {
const group = emoji.list.find(g => g.heading === commitGroup.title);
const other = emoji.list.find(g => g.heading === otherCommitGroup.title);
const group = translator.findGroupByHeading(commitGroup.title); //emoji.list.find(g => g.heading === commitGroup.title);
const other = translator.findGroupByHeading(otherCommitGroup.title); // emoji.list.find(g => g.heading === otherCommitGroup.title);

return group.index === other.index
? group.heading
Expand Down
4 changes: 3 additions & 1 deletion src/conventional-changelog/factory/get-writer-opts.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const emoji = require("../../emoji/emoji");
const config = require("../../config/config");
const translator = require("../../translation/translator")();
const commitGroupsSort = require("./commit-groups-sort");

/**
Expand All @@ -11,11 +12,12 @@ module.exports = function getWriterOpts() {
return {
transform(commit, context) {
let issues = [];

if (commit.emoji != null) {
const group = emoji.findEmoji(commit.emoji);
if (group == null || !group.inChangelog) return null;

commit.type = group.heading;
commit.type = translator.translateHeading(group);
} else {
// Skip without emoji
return null;
Expand Down
3 changes: 1 addition & 2 deletions src/conventional-changelog/parser-opts.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
"use strict";

const emojiRegex = require("emoji-regex/text")();
const emoji = require("../emoji/emoji");

const pattern = new RegExp(`^(${emojiRegex.source})(\\s*)(.*)$`);
const pattern = emoji.headerRegex;

module.exports = {
headerPattern: pattern,
Expand Down
20 changes: 16 additions & 4 deletions src/emoji/emoji.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const emojiGroups = require("./emoji-groups");
const emojiConfigLoader = require("./emoji-config-loader");

const emojiRegex = require("emoji-regex/text")();

/**
Expand Down Expand Up @@ -82,15 +83,26 @@ function normalizeEmojiGroup(group) {
const baseGroups = emojiGroups.map(normalizeEmojiGroup);
const groups = emojiConfigLoader(baseGroups).map(normalizeEmojiGroup);

const commitRegex = new RegExp(
`^(${emojiRegex.source})(\\s*)([\\s\\S]*)$`,
"g"
/**
* We create a regex based on provided Emoji (and their Emoji Aliases)
* in order to fix bugs with `emoji-regex` regex, atleast to be sure
* changelog emojis are always available.
*/
const groupsEmojiRegex = groups
.reduce((emojis, g) => {
emojis.push(g.emoji, ...g.aliases);
return emojis;
}, [])
.join("|");

const headerRegex = new RegExp(
`^(${groupsEmojiRegex}|${emojiRegex.source})(\\s*)(.*)$`
);

module.exports = {
list: groups,
baseList: baseGroups,
commitRegex,
headerRegex,
featureEmojis: emojisByBump("minor"),
breakingEmojis: emojisByBump("major"),
releaseEmoji: groups.find(g => g.type === "release"),
Expand Down
3 changes: 2 additions & 1 deletion src/lint/lint-commit-message.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const config = require("../config/config");
const parseCommit = require("./parse/parse-commit");
const linter = require("./rules/shared/linter");
const translator = require("../translation/translator")();
const formatLintIssues = require("./format/format-lint-issues");

const rules = [
Expand Down Expand Up @@ -62,7 +63,7 @@ function lintCommitMessage(commit) {

// An error occurred.
result.errors.push({
...partial,
message: translator.translateRule(rule.name, ...partial.args),
severity: options.severity,
rule: rule.name
});
Expand Down
2 changes: 1 addition & 1 deletion src/lint/rules/body-leading-blank.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ module.exports = {
// console.log(tokens.commit.match(BODY_LEADING_BLANK_REGEX));
if (BODY_LEADING_BLANK_REGEX.test(tokens.commit)) return null;

return linter.error(`Body should begin with a leading blank line.`);
return linter.error();
}
};
6 changes: 1 addition & 5 deletions src/lint/rules/emoji-from-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ module.exports = {
const type = matches[1].toLowerCase();
const group = emoji.findEmojiByType(type);
if (!group) {
return linter.error(
`Type Alias "${type}" is not allowed. It should be one of: "${emoji.list
.map(e => e.type)
.join(", ")}"`
);
return linter.error(type, emoji.list.map(e => e.type).join(", "));
}

// Remove `fix` ~> `fix:` ~ `🐛`
Expand Down
4 changes: 1 addition & 3 deletions src/lint/rules/emoji-known.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ module.exports = {

if (emojis.findEmoji(tokens.emoji)) return null;

return linter.error(
`Emoji "${tokens.emoji}" is not allowed. It should be one of: ${emojis.list.map(l => l.emoji).join(", ")}` // prettier-ignore
);
return linter.error(tokens.emoji, emojis.list.map(l => l.emoji).join(", "));
}
};
4 changes: 1 addition & 3 deletions src/lint/rules/emoji-require.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ module.exports = {
rule: function(ctx, options, tokens) {
if (tokens.emoji) return null;

return linter.error(
`Emoji is required, but it's not present in "${tokens.header}".` // prettier-ignore
);
return linter.error(tokens.header);
}
};
4 changes: 1 addition & 3 deletions src/lint/rules/header-max-length.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ module.exports = {
return null;
}

return linter.error(
`Header max length is ${options.args.max} characters, ${tokens.header.length} provided.` // prettier-ignore
);
return linter.error(options.args.max, tokens.header.length);
}
};
4 changes: 2 additions & 2 deletions src/lint/rules/shared/linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ function resolveRuleOptions(config, rule) {
};
}

function error(message) {
function error(...args) {
return {
message: message
args
};
}

Expand Down
4 changes: 1 addition & 3 deletions src/lint/rules/subject-require.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ module.exports = {
rule: function(ctx, options, tokens) {
if (tokens.subject) return null;

return linter.error(
`Subject is required, but it's not present in "${tokens.header}".` // prettier-ignore
);
return linter.error(tokens.header);
}
};
13 changes: 13 additions & 0 deletions src/translation/find-group-by-heading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const emoji = require("../emoji/emoji");

module.exports = function findGroupByHeading(strings, heading) {
const translatedType = Object.keys(strings.headings).find(
key => strings.headings[key] === heading
);

if (translatedType) {
return emoji.list.find(e => e.type === translatedType);
}

return emoji.list.find(e => e.heading === heading);
};
19 changes: 19 additions & 0 deletions src/translation/get-language-strings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const fs = require("fs");
const config = require("../config/config");

const stringFiles = fs.readdirSync(__dirname + "/strings");
const languages = stringFiles.map(f => f.replace(".js", ""));

module.exports = function getLanguageStrings() {
const language = config.language || "en";

if (languages.indexOf(language) === -1) {
throw new Error(
`Language "${language}" is not available. Please use on of: ${languages.join(
", "
)}`
);
}

return require(`${__dirname}/strings/${language}.js`);
};
13 changes: 13 additions & 0 deletions src/translation/strings/en.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
headings: {
/** Already provided in emoji-groups */
},
rules: {
"body-leading-blank": "Body should begin with a leading blank line.",
"emoji-from-type": `Type Alias "$0" is not allowed. It should be one of: $1.`,
"emoji-known": `Emoji "$0" is not allowed. It should be one of: $1.`,
"emoji-require": `Emoji is required, but it's not present in "$0".`,
"header-max-length": `Header max length is $0 characters, $1 provided.`,
"subject-require": `Subject is required, but it's not present in "$0".`
}
};
22 changes: 22 additions & 0 deletions src/translation/strings/it.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module.exports = {
headings: {
fix: `🐛 Bug Fix`,
improvement: `🛠 Migliorie`,
feat: `✨ Nuove Funzionalità`,
chore: `🏗 Modifiche Interne`,
perf: `⚡️ Performance`,
refactor: `♻️ Refactor`,
docs: `📚 Documentazione`,
breaking: `🚨 Breaking Change`,
security: `🔓 Sicurezza`
},
rules: {
"body-leading-blank":
"Il <body> del commit deve iniziare con una nuova linea (\\n)",
"emoji-from-type": `Il tipo "$0" non è permesso. Deve essere uno fra: $1.`,
"emoji-known": `L'Emoji "$0" non è permessa. Deve essere una fra: $1.`,
"emoji-require": `L'Emoji è richiesta, ma non è presente nè deducibile dal tipo in "$0".`,
"header-max-length": `La lunghezza massima dell'intestazione è di $0 caratteri, $1 forniti.`,
"subject-require": `Il Soggetto è richiesto, ma non è presente in "$0".`
}
};
8 changes: 8 additions & 0 deletions src/translation/translate-heading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Provides heading translation based on config locale
*/
module.exports = function translateHeading(strings, group) {
return strings.headings[group.type]
? strings.headings[group.type]
: group.heading;
};
10 changes: 10 additions & 0 deletions src/translation/translate-rule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Given a string, translate rule message passing arguments and replacing
* theme inside the string given their index, i.e. `$0` for the first arg, etc.
*/
module.exports = function translateRule(strings, rule, ...args) {
return strings.rules[rule].replace(
/\$([0-9]+)/,
(match, index) => args[parseInt(index, 10)]
);
};
14 changes: 14 additions & 0 deletions src/translation/translator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const getLanguageStrings = require("./get-language-strings");
const translateHeading = require("./translate-heading");
const translateRule = require("./translate-rule");
const findGroupByHeading = require("./find-group-by-heading");

module.exports = function() {
const strings = getLanguageStrings();

return {
translateHeading: group => translateHeading(strings, group),
translateRule: (rule, args) => translateRule(strings, rule, args),
findGroupByHeading: heading => findGroupByHeading(strings, heading)
};
};

0 comments on commit 0dba422

Please sign in to comment.