Skip to content

Commit

Permalink
Merge pull request #544 from atls/feat/locales-generator
Browse files Browse the repository at this point in the history
feat: add locales generator
  • Loading branch information
TorinAsakura authored Feb 2, 2024
2 parents a1909df + 2454dde commit 9ef7f95
Show file tree
Hide file tree
Showing 19 changed files with 326 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 74 additions & 0 deletions generators/locales/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# LOCALES-GENERATOR

Скрипт для автоматической генерации переводов `react-inl`.

Создаёт на уровне энтрипоинта `locales/[localName].json` файл, содержащий все переводы энтрипоинта на основе `id` и `defaultMessages`

Для работы обязательно указывать в `formatMessage` или `FormattedMessage` уникальный `id` и текст перевода в `defaultMessages`.

Пример валидного компонента:

```html
<Hello
hello={formatMessage({ id: 'app.home_page.hello', defaultMessage: 'HELLO FROM HOME' })}
/>
<Text>
<FormattedMessage id='app.home_page.content' defaultMessage='CONTENT' />
</Text>
```

---

## Установка

1. В каждый энтрипоинт, где используются переводы, устанавливаем пакет: `yarn add -D @atls-ui-generators/locales`
2. Добавляем в корневой `package.json` проекта следующий скрипт:
```json
{
"scripts": {
"postinstall": "yarn workspaces changed foreach --parallel run generate-locales"
}
}
```
3. Вносим изменения в энтрипоинты и вызываем `yarn postinstall` из корня проекта.

_\* достаточно изменить хотя бы один компонент, например фрагмент, в каждом энтрипоинте_

---

## Запуск

Вызов `yarn postinstall` из корня проекта вызовет генерацию переводов для энтрипоинтов, если в них произошли изменения.

Вызов `yarn generate-locales` из энтрипоинта вызовет генерацию для исходного энтрипоинта.

После успешной генерации в проекте должны сгенерироваться папки `locales`, содержащие актуальные переводы.

### Входные аргументы

`generate-locales` принимает следующие аргументы:

1. **componentPaths** - Относительный список путей от `package.json` энтрипоинта по которым будет производится поиск переводов \*_необязательный_.
- Принимается неограниченное кол-во аргументов. "generate-locales path1 path2 ...pathN"
- Если не указать **componentPaths** , скрипт будет проходить по фрагментам и страницам текущего энтрипоинта
2. **localName** - Название локали \*_необязательный_.
- Указывается после `-out=`
- Если не указывать **localName**, то умолчанию берётся локаль `ru`

Если необходимо вызвать скрипт с параметрами отличными от исходных, то в энтрипоинт необходимо добавить новый скрипт.

Пример расширенного скрипта:

```json
{
"scripts": {
"generate-locales": "generate-locales ../test-folder --out=en"
}
}
```

---

# Changelog

---
34 changes: 34 additions & 0 deletions generators/locales/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@atls-ui-generators/locales",
"version": "0.0.0",
"license": "BSD-3-Clause",
"main": "src/index.ts",
"bin": {
"generate-locales": "dist/generator.js"
},
"files": [
"dist"
],
"scripts": {
"build": "yarn library build",
"prepack": "yarn run build",
"postpack": "rm -rf dist"
},
"dependencies": {
"@atls/config-prettier": "0.0.5",
"@atls/prettier-plugin": "0.0.7",
"@babel/standalone": "7.22.20",
"camelcase": "6.3.0",
"commander": "9.5.0",
"prettier": "2.8.8"
},
"devDependencies": {
"@types/babel__standalone": "7.1.6",
"@types/prettier": "2.7.3"
},
"publishConfig": {
"access": "public",
"main": "dist/index.js",
"typings": "dist/index.d.ts"
}
}
22 changes: 22 additions & 0 deletions generators/locales/src/generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { defaultPaths } from './locales-generator.constants'
import { mergeLocales } from './merge-locales'
import { processDirectory } from './process-directory'

const allLocales = []
let outputFile = 'ru'
const argPaths: string[] = []

// @ts-ignore
process.argv.slice(2).forEach((arg) => {
if (!arg.startsWith('--out=')) {
argPaths.push(arg)
} else {
// eslint-disable-next-line prefer-destructuring
outputFile = arg.split('=')[1]
}
})
const paths = argPaths.length ? argPaths : defaultPaths

paths.forEach((path) => processDirectory(path, 'locales', allLocales, outputFile))

mergeLocales(allLocales, `./locales/${outputFile}.json`)
1 change: 1 addition & 0 deletions generators/locales/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './generator'
1 change: 1 addition & 0 deletions generators/locales/src/locales-generator.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const defaultPaths = ['../../fragments', '../../pages']
1 change: 1 addition & 0 deletions generators/locales/src/merge-locales/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './merge-locales'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type MergeLocalesType = (files: string[], outputPath: string) => void
26 changes: 26 additions & 0 deletions generators/locales/src/merge-locales/merge-locales.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { existsSync } from 'fs'
import { readFileSync } from 'fs'
import { mkdirSync } from 'fs'
import { writeFileSync } from 'fs'
import { dirname } from 'path'

import { MergeLocalesType } from './merge-locales.interfaces'

export const mergeLocales: MergeLocalesType = (files, outputPath) => {
if (!files.length) return

const mergedLocales = {}
files.forEach((file) => {
if (existsSync(file)) {
const content = JSON.parse(readFileSync(file, 'utf8'))
Object.assign(mergedLocales, content)
}
})
const directory = dirname(outputPath)

if (!existsSync(directory)) {
mkdirSync(directory, { recursive: true })
}

writeFileSync(outputPath, JSON.stringify(mergedLocales, null, 2), 'utf8')
}
1 change: 1 addition & 0 deletions generators/locales/src/process-directory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './process-directory'
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type ProcessDirectoryType = (
startPath: string,
folderName: string,
allLocales: string[],
outputLocale: string
) => void
37 changes: 37 additions & 0 deletions generators/locales/src/process-directory/process-directory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* eslint-disable no-console */

import { execSync } from 'child_process'
import { existsSync } from 'fs'
import { readdirSync } from 'fs'
import { join } from 'path'

import { ProcessDirectoryType } from './process-directory.interfaces'
import { removeEmptyLocale } from '../remove-empty-locale'

export const processDirectory: ProcessDirectoryType = (
startPath,
folderName,
allLocales,
outputLocale
) => {
if (!existsSync(startPath)) {
console.error(new Error(`No directory ${startPath}`))
return
}

const directories = readdirSync(startPath, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name)

directories.forEach((dir) => {
const localePath = join(startPath, dir)
if (existsSync(localePath)) {
const outputFilePath = `${localePath}/${folderName}/${outputLocale}.json`
const command = `formatjs extract "${localePath}/**/*.tsx" --out-file "${outputFilePath}" --format simple`
console.log(`Running: ${command}`)
execSync(command, { stdio: 'inherit' })

removeEmptyLocale(outputFilePath, localePath, folderName, allLocales)
}
})
}
1 change: 1 addition & 0 deletions generators/locales/src/remove-empty-locale/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './remove-empty-locale'
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type RemoveEmptyLocaleType = (
outputFilePath: string,
localePath: string,
folderName: string,
allLocales: string[]
) => void
28 changes: 28 additions & 0 deletions generators/locales/src/remove-empty-locale/remove-empty-locale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { existsSync } from 'fs'
import { readFileSync } from 'fs'
import { readdirSync } from 'fs'
import { rmSync } from 'fs'
import { unlinkSync } from 'fs'
import { join } from 'path'

import { RemoveEmptyLocaleType } from './remove-empty-locale.interfaces'

export const removeEmptyLocale: RemoveEmptyLocaleType = (
outputFilePath,
localePath,
folderName,
allLocales
) => {
if (existsSync(outputFilePath)) {
const content = readFileSync(outputFilePath, 'utf8').trim()
if (content === '{}' || content === '{\n}') {
unlinkSync(outputFilePath)

if (readdirSync(join(localePath, folderName)).length === 0) {
rmSync(join(localePath, folderName), { recursive: true, force: true })
}
} else {
allLocales.push(outputFilePath)
}
}
}
42 changes: 42 additions & 0 deletions generators/locales/src/unit/locales-generator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { existsSync } from 'fs'
import { readFileSync } from 'fs'

import { mergeLocales } from '../merge-locales'
import { removeEmptyLocale } from '../remove-empty-locale'

describe('Locale processing script', () => {
describe('mergeLocales', () => {
const outputFileName = 'merged'
const outputPath = `generators/locales/src/unit/locales/${outputFileName}.json`

it('should merge locale files into one', () => {
mergeLocales(
[
'generators/locales/src/unit/mocks/test.json',
'generators/locales/src/unit/mocks/test1.json',
],
outputPath
)

const mergedContent = JSON.parse(readFileSync(outputPath, 'utf8'))
expect(mergedContent).toEqual({
test_id: 'test_message',
test_id1: 'test_message1',
})
})
it('should remove empty locale file', () => {
mergeLocales([''], outputPath)

const allLocales = []
const mergedContent = JSON.parse(readFileSync(outputPath, 'utf8'))
expect(mergedContent).toEqual({})
removeEmptyLocale(
'generators/locales/src/unit/locales/merged.json',
'generators/locales/src/unit',
'locales',
allLocales
)
expect(existsSync(outputPath)).toEqual(false)
})
})
})
3 changes: 3 additions & 0 deletions generators/locales/src/unit/mocks/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"test_id": "test_message"
}
3 changes: 3 additions & 0 deletions generators/locales/src/unit/mocks/test1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"test_id1": "test_message1"
}
17 changes: 17 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,23 @@ __metadata:
languageName: unknown
linkType: soft

"@atls-ui-generators/locales@workspace:generators/locales":
version: 0.0.0-use.local
resolution: "@atls-ui-generators/locales@workspace:generators/locales"
dependencies:
"@atls/config-prettier": "npm:0.0.5"
"@atls/prettier-plugin": "npm:0.0.7"
"@babel/standalone": "npm:7.22.20"
"@types/babel__standalone": "npm:7.1.6"
"@types/prettier": "npm:2.7.3"
camelcase: "npm:6.3.0"
commander: "npm:9.5.0"
prettier: "npm:2.8.8"
bin:
generate-locales: dist/generator.js
languageName: unknown
linkType: soft

"@atls-ui-generators/utils@workspace:*, @atls-ui-generators/utils@workspace:generators/utils":
version: 0.0.0-use.local
resolution: "@atls-ui-generators/utils@workspace:generators/utils"
Expand Down

0 comments on commit 9ef7f95

Please sign in to comment.