Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@betomorrow/i18n-typegen

Package Overview
Dependencies
Maintainers
0
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@betomorrow/i18n-typegen - npm Package Compare versions

Comparing version 1.1.0 to 2.0.0

dist/rules/key.js

21

dist/command/init.js

@@ -38,2 +38,23 @@ "use strict";

},
rules: [
{
"//": "Add pluralization placeholders",
condition: { keyEndsWith: ["zero", "one", "other"] },
transformer: {
addPlaceholder: { name: "count", type: ["number"] },
removeLastPart: true,
},
},
{
"//": "Add interpolation values for matched placeholders",
condition: { placeholderPattern: { prefix: "{{", suffix: "}}" } },
transformer: {
addMatchedPlaceholder: { type: ["string", "number"] },
},
},
],
extra: {
prettierIgnore: true,
eslintDisablePrettier: false,
},
};

@@ -40,0 +61,0 @@ console.log(`Initialize config file ${configurationFilename}\n`);

5

dist/templates/generate-type.js

@@ -62,6 +62,7 @@ "use strict";

const wordings = (0, wording_loader_1.loadWordings)(configuration);
const templateData = (0, generate_template_data_1.generateTemplateData)(wordings);
const generatedType = mustache_1.default.render(template, Object.assign({ keys: templateData }, utilityChar));
const templateData = (0, generate_template_data_1.generateTemplateData)(wordings, configuration);
console.log(JSON.stringify(template, null, 2));
const generatedType = mustache_1.default.render(template, Object.assign(Object.assign({ keys: templateData }, utilityChar), configuration.extra));
writeFile(generatedType, configuration.output.path);
}
exports.generateType = generateType;

127

dist/translation/generate-template-data.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateTemplateData = void 0;
const find_interpolation_1 = require("./find-interpolation");
const is_enumerable_1 = require("./is-enumerable");
const defaultConfiguration = {
detectPlurial: true,
detectInterpolation: true,
};
function generateTemplateData(translations, overwriteConfiguration = {}) {
const config = Object.assign(Object.assign({}, defaultConfiguration), overwriteConfiguration);
const entries = new Map();
const key_1 = require("../rules/key");
const translation_1 = require("../rules/translation");
function createEntry(item) {
return {
key: item.key,
translation: item.translation,
interpolations: new Map(),
};
}
function generateTemplateData(translations, configuration) {
const entries = [];
Object.entries(translations).forEach(([key, translation]) => {
const entry = processTranslation(key, translation, entries, config);
entries.set(entry.key, entry);
const entry = processTranslation(createEntry({ key, translation }), configuration);
entries.push(entry);
});
return mapToTemplateData(entries);
return toTemplateData(entries);
}
exports.generateTemplateData = generateTemplateData;
function mapToTemplateData(entries) {
return Array.from(entries.values()).map((it) => ({
key: it.key,
interpolations: interpolationsMapToTemplate(it.interpolations),
}));
}
function interpolationsMapToTemplate(interpolations) {
const interpolationArray = Array.from(interpolations.entries()).map(([name, type]) => ({ name, type }));
if (interpolationArray.length > 0)
interpolationArray[interpolationArray.length - 1].last = true;
return interpolationArray;
}
function processTranslation(key, translation, entries, configuration) {
const { detectPlurial, detectInterpolation } = configuration;
const interpolationsNames = (0, find_interpolation_1.findInterpolations)(translation);
const interpolations = detectInterpolation
? new Map(interpolationsNames.map((name) => [name, "string"]))
: new Map();
if (detectPlurial && (0, is_enumerable_1.isEnumerable)(key)) {
const shrunkKey = removeLastPart(key);
interpolations.set("count", "number");
if (entries.has(shrunkKey)) {
// If the entry already exists, merge interpolations
const existingEntry = entries.get(shrunkKey);
return {
key: shrunkKey,
interpolations: mergeInterpolations(existingEntry.interpolations, interpolations),
};
function toTemplateData(entries) {
var _a, _b;
const entryMap = new Map();
for (const entry of entries) {
// Retrieve or initialize the interpolations for the current entry key
let interpolations = (_b = (_a = entryMap.get(entry.key)) === null || _a === void 0 ? void 0 : _a.interpolations) !== null && _b !== void 0 ? _b : [];
// Add new interpolations from the entry
for (const [name, types] of entry.interpolations) {
const existingInterpolation = interpolations.find((interpol) => interpol.name === name);
if (existingInterpolation) {
// Merge types if the interpolation already exists
existingInterpolation.type = mergeUniqueTypes(existingInterpolation.type, types);
}
else {
// Add new interpolation
interpolations.push({
name,
type: types.map((t) => ({ value: t })),
});
}
}
else {
return { key: shrunkKey, interpolations };
// Update the entry map
entryMap.set(entry.key, { key: entry.key, interpolations });
}
// Set the `last` property
for (const entry of entryMap.values()) {
const lastInterpolationIndex = entry.interpolations.length - 1;
if (lastInterpolationIndex >= 0) {
entry.interpolations[lastInterpolationIndex].last = true;
}
for (const interpolation of entry.interpolations) {
const lastTypeIndex = interpolation.type.length - 1;
if (lastTypeIndex >= 0) {
interpolation.type[lastTypeIndex].last = true;
}
}
}
return { key, interpolations };
return Array.from(entryMap.values());
}
const removeLastPart = (key, delimiter = ".") => key.split(delimiter).slice(0, -1).join(delimiter);
function mergeInterpolations(existingInterpolations, newInterpolations) {
const mergedInterpolations = new Map([...existingInterpolations]);
newInterpolations.forEach((type, name) => {
if (!mergedInterpolations.has(name)) {
mergedInterpolations.set(name, type);
function mergeUniqueTypes(existingTypes, newTypes) {
const existingTypeValues = new Set(existingTypes.map((t) => t.value));
for (const newType of newTypes) {
if (!existingTypeValues.has(newType)) {
existingTypes.push({ value: newType });
}
}
return existingTypes;
}
function processTranslation(entry, configuration) {
let result = entry;
// Apply key rules first
const sortedRules = configuration.rules.sort((a, b) => {
if ((0, key_1.isKeyRule)(a) && !(0, key_1.isKeyRule)(b)) {
return -1;
}
if ((0, key_1.isKeyRule)(b) && !(0, key_1.isKeyRule)(a)) {
return 1;
}
return 0;
});
return mergedInterpolations;
for (const rule of sortedRules) {
if ((0, key_1.isKeyRule)(rule)) {
result = (0, key_1.applyKeyEndsWithRule)(rule, result);
}
if ((0, translation_1.isTranslationRule)(rule)) {
result = (0, translation_1.applyTranslationMatchRule)(rule, result);
}
}
return result;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const generate_template_data_1 = require("./generate-template-data");
const mockConfig = { input: {}, output: {}, rules: [] };
describe("generateType", () => {

@@ -11,7 +12,15 @@ it("should handle pluralization correctly", () => {

};
const result = (0, generate_template_data_1.generateTemplateData)(translations, { detectPlurial: true });
const result = (0, generate_template_data_1.generateTemplateData)(translations, Object.assign(Object.assign({}, mockConfig), { rules: [
{
condition: { keyEndsWith: ["one", "other", "zero"] },
transformer: {
addPlaceholder: { name: "count", type: ["number"] },
removeLastPart: true,
},
},
] }));
expect(result).toHaveLength(1);
expect(result[0]).toEqual({
expect(result[0]).toMatchObject({
key: "day",
interpolations: [{ name: "count", type: "number", last: true }],
interpolations: [{ name: "count", type: [{ value: "number" }] }],
});

@@ -25,11 +34,11 @@ });

};
const result = (0, generate_template_data_1.generateTemplateData)(translations, { detectPlurial: false });
const result = (0, generate_template_data_1.generateTemplateData)(translations, mockConfig);
expect(result).toHaveLength(3);
expect(result[0]).toEqual({
expect(result[0]).toMatchObject({
key: "day.one",
interpolations: [],
});
expect(result[1]).toEqual({
expect(result[1]).toMatchObject({
key: "day.other",
interpolations: [{ name: "count", type: "string", last: true }],
interpolations: [],
});

@@ -41,9 +50,20 @@ });

};
const result = (0, generate_template_data_1.generateTemplateData)(translations);
const result = (0, generate_template_data_1.generateTemplateData)(translations, Object.assign(Object.assign({}, mockConfig), { rules: [
{
condition: { placeholderPattern: { prefix: "{{", suffix: "}}" } },
transformer: { addMatchedPlaceholder: { type: ["string", "number"] } },
},
] }));
expect(result).toHaveLength(1);
expect(result[0]).toEqual({
expect(result[0]).toMatchObject({
key: "greeting",
interpolations: [
{ name: "firstName", type: "string" },
{ name: "familyName", type: "string", last: true },
{
name: "firstName",
type: [{ value: "string" }, { value: "number" }],
},
{
name: "familyName",
type: [{ value: "string" }, { value: "number" }],
},
],

@@ -58,13 +78,49 @@ });

};
const result = (0, generate_template_data_1.generateTemplateData)(translations);
const rules = [
{
condition: { placeholderPattern: { prefix: "{{", suffix: "}}" } },
transformer: { addMatchedPlaceholder: { type: ["string"] } },
},
{
condition: { keyEndsWith: ["one", "other", "zero"] },
transformer: { addPlaceholder: { name: "count", type: ["number"] }, removeLastPart: true },
},
];
const result = (0, generate_template_data_1.generateTemplateData)(translations, Object.assign(Object.assign({}, mockConfig), { rules }));
expect(result).toHaveLength(1);
expect(result[0]).toEqual({
expect(result[0]).toMatchObject({
key: "day",
interpolations: [
{ name: "mood", type: "string" },
{ name: "count", type: "number" },
{ name: "moods", type: "string", last: true },
{ name: "count", type: [{ value: "number" }, { value: "string" }] },
{ name: "mood", type: [{ value: "string" }] },
{ name: "moods", type: [{ value: "string" }] },
],
});
});
it("should handle ", () => {
const translations = {
"greeting.man": "Hello Mr {{name}}!",
"greeting.woman": "Hello Ms {{name}}!",
"greeting.neutral": "Hello {{name}}!",
};
const rules = [
{
condition: { placeholderPattern: { prefix: "{{", suffix: "}}" } },
transformer: { addMatchedPlaceholder: { type: ["string"] } },
},
{
condition: { keyEndsWith: ["man", "woman", "neutral"] },
transformer: { addPlaceholder: { name: "gender", type: ["string"] }, removeLastPart: true },
},
];
const result = (0, generate_template_data_1.generateTemplateData)(translations, Object.assign(Object.assign({}, mockConfig), { rules }));
expect(result).toHaveLength(1);
expect(result[0]).toMatchObject({
key: "greeting",
interpolations: [
{ name: "gender", type: [{ value: "string" }] },
{ name: "name", type: [{ value: "string" }] },
],
});
});
});
{
"name": "@betomorrow/i18n-typegen",
"version": "1.1.0",
"version": "2.0.0",
"description": "Generate TS type for your translations keys and interpolation values",

@@ -10,2 +10,3 @@ "main": "dist/index.js",

"test": "jest",
"snapshot": "node test/test.js",
"build": "tsc -p . && cp ./src/templates/translations.mustache ./dist/templates/translations.mustache"

@@ -33,2 +34,3 @@ },

"devDependencies": {
"@release-it/conventional-changelog": "^8.0.1",
"@types/fs-extra": "^11.0.4",

@@ -38,3 +40,5 @@ "@types/jest": "^29.5.11",

"@types/node": "^20.10.4",
"diff": "^5.2.0",
"jest": "^29.7.0",
"release-it": "^17.6.0",
"ts-jest": "^29.1.1",

@@ -51,3 +55,51 @@ "typescript": "^5.3.3"

"bin/"
]
],
"prettier": {
"arrowParens": "always",
"bracketSpacing": true,
"bracketSameLine": true,
"printWidth": 100,
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false
},
"release-it": {
"git": {
"commitMessage": "chore: release ${version}",
"tagName": "v${version}"
},
"npm": {
"publish": true
},
"github": {
"release": true
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": {
"name": "conventionalcommits",
"types": [
{
"type": "feat",
"section": "✨ Features"
},
{
"type": "fix",
"section": "🐛 Bug Fixes"
},
{
"type": "chore(deps)",
"section": "🛠️ Dependency Upgrades"
},
{
"type": "docs",
"section": "📚 Documentation"
}
]
}
}
}
}
}

@@ -73,7 +73,28 @@ # i18n-typegen

"input": {
"format": "flatten",
"path": "./i18n/en.json"
"format": "nested",
"path": "./input/default.json"
},
"output": {
"path": "./i18n/translations.d.ts"
"path": "./output/default.d.ts"
},
"rules": [
{
"//": "Add pluralization placeholders",
"condition": { "keyEndsWith": ["zero", "one", "other"] },
"transformer": {
"addPlaceholder": { "name": "count", "type": ["number", "string"] },
"removeLastPart": true
}
},
{
"//": "Add interpolation values for matched placeholders",
"condition": { "placeholderPattern": { "prefix": "{{", "suffix": "}}" } },
"transformer": {
"addMatchedPlaceholder": { "type": ["string", "number"] }
}
}
],
"extra": {
"prettierIgnore": true,
"eslintDisablePrettier": false
}

@@ -86,2 +107,5 @@ }

- nested scoped dictionnaries: `{ home: { header: { greeting: "hello" } } }`
- extra de-opt generated files from prettier or eslint prettier rule to comply with specific configurations.
- `prettierIgnore` add `// prettier-ignore` in generated file.
- `eslintDisablePrettier` add `/* eslint-disable prettier/prettier */` in generated file.

@@ -108,6 +132,3 @@ ### Recommended Toolbox

*/
unsafeTranslate: (
key: string,
interpolations?: Record<string, unknown>
) => string;
unsafeTranslate: (key: string, interpolations?: Record<string, unknown>) => string;
};

@@ -114,0 +135,0 @@

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc