-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add
@skyra/i18next-type-generator
- Loading branch information
Showing
15 changed files
with
348 additions
and
2 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
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
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,4 @@ | ||
name: i18next-type-generator | ||
org: skyra | ||
install: true | ||
packagePath: packages/i18next-type-generator |
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,3 @@ | ||
# Changelog | ||
|
||
All notable changes to this project will be documented in this file. |
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,45 @@ | ||
# `@skyra/i18next-type-generator` | ||
|
||
A fast and modern type augmentation generator for the [`@skyra/i18next-backend`](https://www.npmjs.com/package/@skyra/i18next-backend) filesystem-based [`i18next`](https://www.npmjs.com/package/i18next) backend for Node.js. | ||
|
||
## Usage | ||
|
||
```bash | ||
$ i18next-type-generator [options] [source] [destination] | ||
|
||
# Arguments: | ||
# source The directory to generate types from (default: "./src/locales/en-US/") | ||
# destination The directory to generate types to (default: "./src/@types/i18next.d.ts") | ||
# | ||
# Options: | ||
# -V, --version output the version number | ||
# -v, --verbose Verbose output | ||
# --no-prettier Disable prettier | ||
# -h, --help display help for command | ||
``` | ||
|
||
This CLI tool generates a `.d.ts` file with the following structure: | ||
|
||
```typescript | ||
// This file is automatically generated, do not edit it. | ||
import 'i18next'; | ||
|
||
declare module 'i18next' { | ||
interface CustomTypeOptions { | ||
resources: { | ||
'commands/choice': { | ||
name: 'choice'; | ||
description: 'Get a random value from a set of choices'; | ||
// ... | ||
}; | ||
// ... | ||
}; | ||
} | ||
} | ||
``` | ||
|
||
The command reads the JSON files inside a directory writes their contents into the `.d.ts` file. This is needed because `typeof import(pathToJSON)` requires the JSON files to be included in `tsconfig.json`, which may be undesirable, and because it types the keys and their inferred types, but does not load the exact values, which breaks i18next's ability to extract arguments from strings. | ||
|
||
This utility does not provide formatting options, so `prettier` is used under the hood to format it before writing to a file, you may opt-out using `--no-prettier` if you want to use a different tool. | ||
|
||
> **Note**: If you want to customize `i18next`'s `CustomTypeOptions` to add [extra options](https://www.i18next.com/overview/typescript), create a different file, TypeScript will merge the two of them. |
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,63 @@ | ||
[changelog] | ||
header = """ | ||
# Changelog | ||
All notable changes to this project will be documented in this file.\n | ||
""" | ||
body = """ | ||
{% if version %}\ | ||
# [{{ version | trim_start_matches(pat="v") }}]\ | ||
{% if previous %}\ | ||
{% if previous.version %}\ | ||
(https://github.com/skyra-project/archid-components/compare/{{ previous.version }}...{{ version }})\ | ||
{% else %}\ | ||
(https://github.com/skyra-project/archid-components/tree/{{ version }})\ | ||
{% endif %}\ | ||
{% endif %} \ | ||
- ({{ timestamp | date(format="%Y-%m-%d") }}) | ||
{% else %}\ | ||
# [unreleased] | ||
{% endif %}\ | ||
{% for group, commits in commits | group_by(attribute="group") %} | ||
## {{ group | upper_first }} | ||
{% for commit in commits %} | ||
- {% if commit.scope %}\ | ||
**{{commit.scope}}:** \ | ||
{% endif %}\ | ||
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/skyra-project/archid-components/commit/{{ commit.id }}))\ | ||
{% if commit.breaking %}\ | ||
{% for breakingChange in commit.footers %}\ | ||
\n{% raw %} {% endraw %}- 💥 **{{ breakingChange.token }}{{ breakingChange.separator }}** {{ breakingChange.value }}\ | ||
{% endfor %}\ | ||
{% endif %}\ | ||
{% endfor %} | ||
{% endfor %}\n | ||
""" | ||
trim = true | ||
footer = "" | ||
|
||
[git] | ||
conventional_commits = true | ||
filter_unconventional = true | ||
commit_parsers = [ | ||
{ message = "^feat", group = "🚀 Features"}, | ||
{ message = "^fix", group = "🐛 Bug Fixes"}, | ||
{ message = "^docs", group = "📝 Documentation"}, | ||
{ message = "^perf", group = "🏃 Performance"}, | ||
{ message = "^refactor", group = "🏠 Refactor"}, | ||
{ message = "^typings", group = "⌨️ Typings"}, | ||
{ message = "^types", group = "⌨️ Typings"}, | ||
{ message = ".*deprecated", body = ".*deprecated", group = "🚨 Deprecation"}, | ||
{ message = "^revert", skip = true}, | ||
{ message = "^style", group = "🪞 Styling"}, | ||
{ message = "^test", group = "🧪 Testing"}, | ||
{ message = "^chore", skip = true}, | ||
{ message = "^ci", skip = true}, | ||
{ message = "^build", skip = true}, | ||
{ body = ".*security", group = "🛡️ Security"}, | ||
] | ||
filter_commits = true | ||
tag_pattern = "@skyra/i18next-type-generator@[0-9]*" | ||
ignore_tags = "" | ||
topo_order = false | ||
sort_commits = "newest" |
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,64 @@ | ||
{ | ||
"name": "@skyra/i18next-type-generator", | ||
"version": "1.0.0", | ||
"description": "A fast utility that generates the TypeScript augmentation for i18next.", | ||
"author": "@skyra", | ||
"license": "Apache-2.0", | ||
"type": "module", | ||
"main": "dist/cli.js", | ||
"bin": { | ||
"i18next-type-generator": "./dist/cli.js" | ||
}, | ||
"sideEffects": false, | ||
"scripts": { | ||
"build": "tsup", | ||
"watch": "tsup --watch", | ||
"typecheck": "tsc -p tsconfig.eslint.json", | ||
"lint": "eslint src --ext ts --fix -c ../../package.json", | ||
"prepack": "yarn build", | ||
"bump": "cliff-jumper", | ||
"check-update": "cliff-jumper --dry-run" | ||
}, | ||
"dependencies": { | ||
"colorette": "^2.0.20", | ||
"commander": "^11.0.0", | ||
"tslib": "^2.6.0" | ||
}, | ||
"optionalDependencies": { | ||
"prettier": "^3.0.0" | ||
}, | ||
"devDependencies": { | ||
"@favware/cliff-jumper": "^2.1.1", | ||
"@types/node": "^18.17.0", | ||
"i18next": "^23.2.11", | ||
"tsup": "^7.1.0", | ||
"typescript": "^5.1.6" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/skyra-project/archid-components.git", | ||
"directory": "packages/i18next-type-generator" | ||
}, | ||
"files": [ | ||
"dist/" | ||
], | ||
"engines": { | ||
"node": ">=16.9.0", | ||
"npm": ">=8.0.0" | ||
}, | ||
"keywords": [ | ||
"discord", | ||
"api", | ||
"http", | ||
"skyra", | ||
"typescript", | ||
"ts", | ||
"yarn" | ||
], | ||
"bugs": { | ||
"url": "https://github.com/skyra-project/archid-components/issues" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
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 @@ | ||
#!/usr/bin/env node | ||
|
||
import { Command, InvalidArgumentError } from 'commander'; | ||
import { readFile } from 'node:fs/promises'; | ||
import { generate } from './generate.js'; | ||
|
||
const cli = new Command(); | ||
|
||
const packageFile = new URL('../package.json', import.meta.url); | ||
const packageJson = JSON.parse(await readFile(packageFile, 'utf-8')); | ||
|
||
const indentationParser = (value: string) => { | ||
if (value === 'tabs') return '\t'; | ||
|
||
const parsed = Number(value); | ||
if (Number.isNaN(parsed)) throw new InvalidArgumentError('The indentation must be a number or "tabs"'); | ||
return ' '.repeat(parsed); | ||
}; | ||
|
||
cli.name('i18next-type-generator') // | ||
.version(packageJson.version) | ||
.argument('[source]', 'The directory to generate types from', './src/locales/en-US/') | ||
.argument('[destination]', 'The directory to generate types to', './src/@types/i18next.d.ts') | ||
.option('-i, --indentation <value>', 'The indentation to use', indentationParser, '\t') | ||
.option('-v, --verbose', 'Verbose output') | ||
.option('--no-prettier', 'Disable prettier') | ||
.action((...args) => generate(args, cli.opts())); | ||
|
||
cli.parse(process.argv); |
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,84 @@ | ||
import { gray, green, italic } from 'colorette'; | ||
import { mkdir, opendir, readFile, writeFile } from 'node:fs/promises'; | ||
import { dirname, join, resolve } from 'node:path'; | ||
import { inspect } from 'node:util'; | ||
|
||
const ci = 'CI' in process.env && process.env.CI !== 'false'; | ||
|
||
const FileIcon = ci ? '📄' : '\uE628'; | ||
const DirectoryIcon = ci ? '📂' : '\uF413'; | ||
|
||
const i1 = '\t'; | ||
const i2 = i1.repeat(2); | ||
const i3 = i1.repeat(3); | ||
|
||
/** | ||
* Recursively walk through the directory and generate the types. | ||
* @param path The path to walk through. | ||
* @param namespace The namespace to use. | ||
*/ | ||
async function recurse(lines: string[], path: string, namespace: string, options: GenerateOptions) { | ||
if (options.verbose) console.log(gray(`Reading directory ${DirectoryIcon} ${green(path)}...`)); | ||
|
||
for await (const dirent of await opendir(path)) { | ||
const file = join(path, dirent.name); | ||
if (dirent.isFile()) { | ||
if (!dirent.name.endsWith('.json')) continue; | ||
if (options.verbose) console.log(gray(`Processing ${FileIcon} ${green(file)}...`)); | ||
|
||
const name = dirent.name.slice(0, -5); | ||
const key = namespace ? `'${namespace}/${name}'` : name; | ||
const data = JSON.stringify(JSON.parse(await readFile(file, 'utf8')), undefined, i1); | ||
lines.push(`${i3}${key}: ${data.replaceAll('\n', `\n${i3}`)};`); | ||
} else if (dirent.isDirectory()) { | ||
await recurse(lines, file, namespace ? `${namespace}/${dirent.name}` : dirent.name, options); | ||
} | ||
} | ||
} | ||
|
||
export async function generate([source, destination]: string[], options: GenerateOptions) { | ||
const sourceDirectory = resolve(source); | ||
const destinationFile = resolve(destination); | ||
|
||
if (options.verbose) { | ||
const lines = [ | ||
`Source: ${DirectoryIcon} ${green(sourceDirectory)}...`, | ||
`Output: ${FileIcon} ${green(destinationFile)}...`, | ||
'', | ||
'Options:', | ||
` - Verbose: ${green(options.verbose ? 'yes' : 'no')}` | ||
]; | ||
console.log(italic(gray(lines.join('\n')))); | ||
} | ||
|
||
const lines = [ | ||
'// This file is automatically generated, do not edit it.', | ||
"import 'i18next';", | ||
'', | ||
"declare module 'i18next' {", | ||
`${i1}interface CustomTypeOptions {`, | ||
`${i2}resources: {` | ||
]; | ||
|
||
await recurse(lines, sourceDirectory, '', options); | ||
lines.push(`${i2}};`, `${i1}}`, '}', ''); | ||
|
||
let generatedSource = lines.join('\n'); | ||
if (options.prettier) { | ||
if (options.verbose) console.log(gray(`Loading prettier...`)); | ||
|
||
const { resolveConfig, format } = await import('prettier'); | ||
const config = await resolveConfig(destinationFile); | ||
if (options.verbose) console.log(gray(`Formatting with prettier config: ${inspect(config, { colors: true })}`)); | ||
|
||
generatedSource = await format(generatedSource, { ...config, filepath: destinationFile }); | ||
} | ||
|
||
await mkdir(dirname(destinationFile), { recursive: true }); | ||
await writeFile(destinationFile, generatedSource, 'utf8'); | ||
} | ||
|
||
interface GenerateOptions { | ||
verbose: boolean; | ||
prettier: boolean; | ||
} |
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 @@ | ||
{ | ||
"extends": "../../../tsconfig.base.json", | ||
"compilerOptions": { | ||
"rootDir": "./", | ||
"outDir": "../dist", | ||
"target": "ES2021", | ||
"moduleResolution": "Node16" | ||
}, | ||
"include": ["."] | ||
} |
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,8 @@ | ||
{ | ||
"extends": "../../tsconfig.eslint.json", | ||
"include": ["src", "tsup.config.ts"], | ||
"compilerOptions": { | ||
"module": "Node16", | ||
"moduleResolution": "node16" | ||
} | ||
} |
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,3 @@ | ||
import { createTsupConfig } from '../../scripts/tsup.config.js'; | ||
|
||
export default createTsupConfig({ entry: ['src/cli.ts'], format: ['esm'], target: 'es2022' }); |
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
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
Oops, something went wrong.