-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Loading status checks…
Merge pull request #1 from BeTomorrow/develop
Implementation of typegen
Showing
24 changed files
with
7,306 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
name: Unit Tests | ||
|
||
on: | ||
push: | ||
branches: [main, develop] | ||
pull_request: | ||
branches: [main] | ||
|
||
jobs: | ||
Jest: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-node@v3 | ||
with: | ||
node-version: 16 | ||
- name: Install Package | ||
run: npm ci --force | ||
- name: Run Package Unit tests | ||
run: npm test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
#!/usr/bin/env node | ||
require("../dist/index.js"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"input": { | ||
"format": "flatten", | ||
"path": "./i18n/en.json" | ||
}, | ||
"output": { | ||
"path": "./i18n/translations.d.ts" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"greeting": "Hello {{firstName}} {{familyName}}!", | ||
"duration.day.one": "1 day", | ||
"duration.day.other": "{{count}} days", | ||
"duration.day.zero": "0 day" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// prettier-ignore | ||
|
||
/** | ||
* Generated by i18n-type-generator | ||
* https://github.com/BeTomorrow/i18n-type-generator | ||
*/ | ||
|
||
declare module "translations" { | ||
type Translations = { | ||
"greeting": { firstName: string; familyName: string }; | ||
"duration.day": { count: number }; | ||
}; | ||
|
||
type TranslationKeys = keyof Translations; | ||
|
||
type TranslationFunctionArgs<T extends TranslationKeys> = T extends TranslationKeys | ||
? Translations[T] extends undefined | ||
? [key: T] | ||
: [key: T, interpolates: Translations[T]] | ||
: never; | ||
|
||
type TranslationFunction = <T extends TranslationKeys>(...args: TranslationFunctionArgs<T>) => string; | ||
|
||
export { TranslationFunction, TranslationFunctionArgs, TranslationKeys }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module.exports = { | ||
preset: "ts-jest", | ||
testEnvironment: "node", | ||
testMatch: ["**/*.test.ts"], | ||
}; |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
{ | ||
"name": "@betomorrow/i18n-typegen", | ||
"version": "1.0.0", | ||
"description": "Generate TS type for your translations keys and interpolation values", | ||
"main": "dist/index.js", | ||
"bin": "bin/i18n-typegen", | ||
"scripts": { | ||
"start": "ts-node src/index.ts", | ||
"test": "jest", | ||
"build": "tsc -p . && cp ./src/templates/translations.mustache ./dist/templates/translations.mustache" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/BeTomorrow/i18n-type-generator.git" | ||
}, | ||
"keywords": [ | ||
"i18n", | ||
"type", | ||
"keys", | ||
"interpolation" | ||
], | ||
"author": "Fabien Drault", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/BeTomorrow/i18n-type-generator/issues" | ||
}, | ||
"homepage": "https://github.com/BeTomorrow/i18n-type-generator#readme", | ||
"devDependencies": { | ||
"@types/fs-extra": "^11.0.4", | ||
"@types/jest": "^29.5.11", | ||
"@types/mustache": "^4.2.5", | ||
"@types/node": "^20.10.4", | ||
"jest": "^29.7.0", | ||
"ts-jest": "^29.1.1", | ||
"typescript": "^5.3.3" | ||
}, | ||
"dependencies": { | ||
"commander": "^11.1.0", | ||
"fs-extra": "^11.2.0", | ||
"mustache": "^4.2.0" | ||
}, | ||
"files": [ | ||
"dist/", | ||
"bin/" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Configuration } from "../config/config-loader"; | ||
import { generateType } from "../templates/generate-type"; | ||
|
||
export function codegen(configuration: Configuration) { | ||
return generateType(configuration); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import * as fs from "fs"; | ||
import { Configuration } from "../config/config-loader"; | ||
|
||
export function init() { | ||
const configurationFilename = "i18n-type.config.json"; | ||
const defaultConfiguration: Configuration = { | ||
input: { | ||
format: "nested", | ||
path: "./i18n/en.json", | ||
}, | ||
output: { | ||
path: "./i18n/translations.d.ts", | ||
}, | ||
}; | ||
console.log(`Initialize config file ${configurationFilename}\n`); | ||
writeDefaultConfiguration(defaultConfiguration, configurationFilename); | ||
} | ||
|
||
function writeDefaultConfiguration(config: Configuration, path: string) { | ||
const text = JSON.stringify(config, null, 2); | ||
try { | ||
fs.writeFileSync(path, text); | ||
console.log(`Default configuration: \n\ | ||
${text}\n`); | ||
} catch (error) { | ||
console.error(`Error writing the i18n type: ${(error as Error).message}`); | ||
process.exit(1); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import * as fs from "fs"; | ||
|
||
export function readConfigFile(configPath: string): Configuration { | ||
try { | ||
const configData = fs.readFileSync(configPath, "utf-8"); | ||
return JSON.parse(configData); | ||
} catch (error) { | ||
console.error( | ||
'Missing configuration file. \n\ | ||
Run "npm run i18n-typegen init" to generate one\n' | ||
); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
type InputFormat = "flatten" | "nested"; | ||
|
||
export interface Configuration { | ||
input: { | ||
format: InputFormat; | ||
path: string; | ||
}; | ||
output: { | ||
path: string; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import * as commander from "commander"; | ||
import * as path from "path"; | ||
import { codegen } from "./command/codegen"; | ||
import { init } from "./command/init"; | ||
import { readConfigFile } from "./config/config-loader"; | ||
|
||
function main() { | ||
const program = new commander.Command(); | ||
|
||
program | ||
.version("1.0.0") | ||
.description( | ||
"Generate TS type for your translation keys and interpolations" | ||
); | ||
|
||
program | ||
.command("codegen") | ||
.option( | ||
"-c, --config <path>", | ||
"Path to the config file", | ||
"i18n-type.config.json" | ||
) | ||
.description("Generate i18n types") | ||
.action((cmd) => { | ||
const configPath = path.resolve(cmd.config); | ||
const config = readConfigFile(configPath); | ||
|
||
codegen(config); | ||
}); | ||
|
||
program | ||
.command("init") | ||
.description("Initialize i18n-config file") | ||
.action(() => init()); | ||
|
||
program.parse(process.argv); | ||
|
||
// If no command is specified, show help | ||
if (!process.argv.slice(2).length) { | ||
program.help(); | ||
} | ||
} | ||
|
||
main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import * as fs from "fs"; | ||
import * as fsExtra from "fs-extra"; | ||
import Mustache from "mustache"; | ||
import * as path from "path"; | ||
import { Configuration } from "../config/config-loader"; | ||
import { generateTemplateData } from "../translation/generate-template-data"; | ||
import { loadWordings } from "../wording/wording-loader"; | ||
|
||
function openTemplate() { | ||
try { | ||
const templatePath = path.join(__dirname, "translations.mustache"); | ||
return fs.readFileSync(templatePath, "utf-8"); | ||
} catch (error) { | ||
console.error( | ||
`Error reading or parsing the template file: ${(error as Error).message}` | ||
); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
function writeFile(text: string, path: string) { | ||
try { | ||
fsExtra.outputFileSync(path, text); | ||
} catch (error) { | ||
console.error(`Error writing the i18n type: ${(error as Error).message}`); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
const utilityChar = { | ||
OPEN_BRACE: "{", | ||
CLOSE_BRACE: "}", | ||
}; | ||
|
||
export function generateType(configuration: Configuration) { | ||
const template = openTemplate(); | ||
|
||
const wordings = loadWordings(configuration); | ||
const templateData = generateTemplateData(wordings); | ||
|
||
const generatedType = Mustache.render(template, { | ||
keys: templateData, | ||
...utilityChar, | ||
}); | ||
|
||
writeFile(generatedType, configuration.output.path); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
export interface InterpolationTemplateData { | ||
name: string; | ||
type: "string" | "number"; | ||
last?: boolean; | ||
} | ||
|
||
export interface WordingEntryTemplateData { | ||
key: string; | ||
interpolations: InterpolationTemplateData[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// prettier-ignore | ||
|
||
/** | ||
* Generated by i18n-type-generator | ||
* https://github.com/BeTomorrow/i18n-typegen | ||
*/ | ||
|
||
declare module "translations" { | ||
type Translations = { | ||
{{#keys}} | ||
"{{key}}": {{#interpolations.length}}{{OPEN_BRACE}}{{#interpolations}} {{name}}: {{type}}{{^last}};{{/last}} {{/interpolations}}{{CLOSE_BRACE}}{{/interpolations.length}}{{^interpolations.length}}undefined{{/interpolations.length}}; | ||
{{/keys}} | ||
}; | ||
|
||
type TranslationKeys = keyof Translations; | ||
|
||
type TranslationFunctionArgs<T extends TranslationKeys> = T extends TranslationKeys | ||
? Translations[T] extends undefined | ||
? [key: T] | ||
: [key: T, interpolates: Translations[T]] | ||
: never; | ||
|
||
type TranslationFunction = <T extends TranslationKeys>(...args: TranslationFunctionArgs<T>) => string; | ||
|
||
export { TranslationFunction, TranslationFunctionArgs, TranslationKeys }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { findInterpolations } from "./find-interpolation"; | ||
|
||
test("Find double bracket interpolation", () => { | ||
const input = "Hello {{name}} !"; | ||
const interpolations = findInterpolations(input); | ||
expect(interpolations).toEqual(["name"]); | ||
}); | ||
|
||
test("Find %{value} interpolation", () => { | ||
const input = "Hello %{name} !"; | ||
const interpolations = findInterpolations(input); | ||
expect(interpolations).toEqual(["name"]); | ||
}); | ||
|
||
test("Find multiple interpolations", () => { | ||
const input = "Hello {{firstname}} %{familyName}"; | ||
const interpolations = findInterpolations(input); | ||
expect(interpolations).toEqual(["firstname", "familyName"]); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/** | ||
* Find all interpolation inside the double bracket. | ||
* Support i18n-js format {{value}} and %{value} | ||
* "Hello {{firstname}} *{familyName}" will return ["firstname", "familyName"] | ||
*/ | ||
export const findInterpolations = (translation: string) => { | ||
const doubleBraceRegexp = /{{(.*?)}}/g; | ||
const percentBraceRegexp = /%{(.*?)}/g; | ||
const matches: string[] = []; | ||
let match; | ||
|
||
while ((match = doubleBraceRegexp.exec(translation)) !== null) { | ||
matches.push(match[1]); | ||
} | ||
while ((match = percentBraceRegexp.exec(translation)) !== null) { | ||
matches.push(match[1]); | ||
} | ||
return matches; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { generateTemplateData } from "./generate-template-data"; | ||
|
||
describe("generateType", () => { | ||
it("should handle pluralization correctly", () => { | ||
const translations = { | ||
"day.one": "1 day", | ||
"day.other": "{{count}} days", | ||
"day.zero": "0 day", | ||
}; | ||
|
||
const result = generateTemplateData(translations, { detectPlurial: true }); | ||
|
||
expect(result).toHaveLength(1); | ||
|
||
expect(result[0]).toEqual({ | ||
key: "day", | ||
interpolations: [{ name: "count", type: "number", last: true }], | ||
}); | ||
}); | ||
|
||
it("should ignore pluralization when disabled", () => { | ||
const translations = { | ||
"day.one": "1 day", | ||
"day.other": "{{count}} days", | ||
"day.zero": "0 day", | ||
}; | ||
|
||
const result = generateTemplateData(translations, { detectPlurial: false }); | ||
|
||
expect(result).toHaveLength(3); | ||
|
||
expect(result[0]).toEqual({ | ||
key: "day.one", | ||
interpolations: [], | ||
}); | ||
expect(result[1]).toEqual({ | ||
key: "day.other", | ||
interpolations: [{ name: "count", type: "string", last: true }], | ||
}); | ||
}); | ||
|
||
it("should handle multiple interpolations correctly", () => { | ||
const translations = { | ||
greeting: "Hello {{firstName}} {{familyName}}", | ||
}; | ||
|
||
const result = generateTemplateData(translations); | ||
|
||
expect(result).toHaveLength(1); | ||
|
||
expect(result[0]).toEqual({ | ||
key: "greeting", | ||
interpolations: [ | ||
{ name: "firstName", type: "string" }, | ||
{ name: "familyName", type: "string", last: true }, | ||
], | ||
}); | ||
}); | ||
|
||
it("should handle mixed of pluralization and interpolations correctly", () => { | ||
const translations = { | ||
"day.one": "1 {{mood}} day", | ||
"day.other": "{{count}} {{moods}} days", | ||
"day.zero": "0 {{mood}} day", | ||
}; | ||
|
||
const result = generateTemplateData(translations); | ||
|
||
expect(result).toHaveLength(1); | ||
|
||
expect(result[0]).toEqual({ | ||
key: "day", | ||
interpolations: [ | ||
{ name: "mood", type: "string" }, | ||
{ name: "count", type: "number" }, | ||
{ name: "moods", type: "string", last: true }, | ||
], | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { | ||
InterpolationTemplateData, | ||
WordingEntryTemplateData, | ||
} from "../templates/template-type"; | ||
import { findInterpolations } from "./find-interpolation"; | ||
import { isEnumerable } from "./is-enumerable"; | ||
|
||
type WordingKey = string; | ||
type Translation = string; | ||
|
||
interface WordingEntry { | ||
key: WordingKey; | ||
interpolations: Map<string, InterpolationType>; | ||
} | ||
|
||
interface GeneratorConfiguration { | ||
detectPlurial: boolean; | ||
detectInterpolation: boolean; | ||
} | ||
|
||
type InterpolationType = "string" | "number"; | ||
|
||
const defaultConfiguration = { | ||
detectPlurial: true, | ||
detectInterpolation: true, | ||
}; | ||
|
||
export function generateTemplateData( | ||
translations: Record<WordingKey, Translation>, | ||
overwriteConfiguration: Partial<GeneratorConfiguration> = {} | ||
): WordingEntryTemplateData[] { | ||
const config = { ...defaultConfiguration, ...overwriteConfiguration }; | ||
const entries: Map<WordingKey, WordingEntry> = new Map(); | ||
|
||
(Object.entries(translations) as [WordingKey, Translation][]).forEach( | ||
([key, translation]) => { | ||
const entry = processTranslation(key, translation, entries, config); | ||
entries.set(entry.key, entry); | ||
} | ||
); | ||
return mapToTemplateData(entries); | ||
} | ||
|
||
function mapToTemplateData( | ||
entries: Map<string, WordingEntry> | ||
): WordingEntryTemplateData[] { | ||
return Array.from(entries.values()).map((it) => ({ | ||
key: it.key, | ||
interpolations: interpolationsMapToTemplate(it.interpolations), | ||
})); | ||
} | ||
|
||
function interpolationsMapToTemplate( | ||
interpolations: Map<string, InterpolationType> | ||
) { | ||
const interpolationArray: InterpolationTemplateData[] = Array.from( | ||
interpolations.entries() | ||
).map(([name, type]) => ({ name, type })); | ||
if (interpolationArray.length > 0) | ||
interpolationArray[interpolationArray.length - 1].last = true; | ||
return interpolationArray; | ||
} | ||
|
||
function processTranslation( | ||
key: string, | ||
translation: string, | ||
entries: Map<string, WordingEntry>, | ||
configuration: GeneratorConfiguration | ||
): WordingEntry { | ||
const { detectPlurial, detectInterpolation } = configuration; | ||
|
||
const interpolationsNames = findInterpolations(translation); | ||
const interpolations = detectInterpolation | ||
? new Map<string, InterpolationType>( | ||
interpolationsNames.map((name) => [name, "string"]) | ||
) | ||
: new Map(); | ||
|
||
if (detectPlurial && 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 | ||
), | ||
}; | ||
} else { | ||
return { key: shrunkKey, interpolations }; | ||
} | ||
} | ||
return { key, interpolations }; | ||
} | ||
|
||
const removeLastPart = (key: WordingKey, delimiter = ".") => | ||
key.split(delimiter).slice(0, -1).join(delimiter); | ||
|
||
function mergeInterpolations( | ||
existingInterpolations: Map<string, InterpolationType>, | ||
newInterpolations: Map<string, InterpolationType> | ||
): Map<string, InterpolationType> { | ||
const mergedInterpolations = new Map([...existingInterpolations]); | ||
|
||
newInterpolations.forEach((type, name) => { | ||
if (!mergedInterpolations.has(name)) { | ||
mergedInterpolations.set(name, type); | ||
} | ||
}); | ||
|
||
return mergedInterpolations; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/** | ||
* Support i18n-js plurialize format | ||
* Support only Zero, One and Other plural form | ||
* Translations keys ends with .one, .zero and .other | ||
*/ | ||
export const isEnumerable = (key: string) => | ||
[".one", ".zero", ".other"].some((enumeration) => key.endsWith(enumeration)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { flatten } from "./flatten"; | ||
|
||
describe("flatten function", () => { | ||
it("should flatten a nested object", () => { | ||
const nestedObject = { | ||
greeting: { | ||
en: "Hello", | ||
fr: "Bonjour", | ||
nested: { | ||
world: { | ||
en: "World", | ||
fr: "Monde", | ||
}, | ||
}, | ||
}, | ||
goodbye: { | ||
en: "Goodbye", | ||
fr: "Au revoir", | ||
}, | ||
}; | ||
|
||
const expectedFlattenedObject = { | ||
"greeting.en": "Hello", | ||
"greeting.fr": "Bonjour", | ||
"greeting.nested.world.en": "World", | ||
"greeting.nested.world.fr": "Monde", | ||
"goodbye.en": "Goodbye", | ||
"goodbye.fr": "Au revoir", | ||
}; | ||
|
||
const result = flatten(nestedObject); | ||
|
||
expect(result).toEqual(expectedFlattenedObject); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
type NestedObject = { | ||
[key: string]: string | NestedObject; | ||
}; | ||
|
||
export function flatten( | ||
object: NestedObject, | ||
parentKey = "" | ||
): Record<string, string> { | ||
const flattenedObject: Record<string, string> = {}; | ||
|
||
for (const [key, value] of Object.entries(object)) { | ||
const currentKey = parentKey ? `${parentKey}.${key}` : key; | ||
|
||
if (typeof value === "object") { | ||
// Recursively flatten nested keys | ||
Object.assign(flattenedObject, flatten(value, currentKey)); | ||
} else { | ||
flattenedObject[currentKey] = value; | ||
} | ||
} | ||
|
||
return flattenedObject; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import * as fs from "fs"; | ||
import { Configuration } from "../config/config-loader"; | ||
import { flatten } from "./flatten"; | ||
|
||
export function loadWordings(configuration: Configuration) { | ||
const wordings = readWordingFile(configuration.input.path); | ||
|
||
if (configuration.input.format === "nested") { | ||
return flatten(wordings); | ||
} | ||
|
||
return wordings; | ||
} | ||
|
||
function readWordingFile(wordingPath: string) { | ||
try { | ||
const wordings = fs.readFileSync(wordingPath, "utf-8"); | ||
return JSON.parse(wordings); | ||
} catch (error) { | ||
console.error( | ||
`Error reading or parsing the wording file: ${(error as Error).message}` | ||
); | ||
process.exit(1); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "es2015", | ||
"module": "CommonJS", | ||
"strict": true, | ||
"esModuleInterop": true, | ||
"outDir": "./dist" | ||
}, | ||
"include": ["src/**/*.ts"], | ||
"exclude": ["node_modules"] | ||
} |