Repository: akameco/extract-react-intl-messages
Branch: master
Commit: 5e8f220414fe
Files: 66
Total size: 61.9 KB
Directory structure:
gitextract_9ar36m_8/
├── .all-contributorsrc
├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ └── test.yml
├── .gitignore
├── .husky/
│ └── pre-commit
├── .prettierignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── eslint.config.mjs
├── example/
│ ├── basic/
│ │ ├── babel.config.js
│ │ ├── i18n/
│ │ │ ├── .keep
│ │ │ ├── en.json
│ │ │ └── ja.json
│ │ ├── package.json
│ │ └── src/
│ │ ├── App.jsx
│ │ └── messages.js
│ └── with-typescript/
│ ├── babel.config.js
│ ├── i18n/
│ │ ├── en.json
│ │ └── ja.json
│ ├── package.json
│ └── src/
│ ├── App.tsx
│ └── messages.ts
├── jest.config.js
├── license
├── package.json
├── readme.md
├── src/
│ ├── cli.ts
│ ├── extract-react-intl/
│ │ ├── index.ts
│ │ ├── readme.md
│ │ └── test/
│ │ ├── __snapshots__/
│ │ │ └── test.ts.snap
│ │ ├── fixtures/
│ │ │ ├── .babelrc
│ │ │ ├── components/
│ │ │ │ ├── App/
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── messages.js
│ │ │ │ ├── Greeting/
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── messages.js
│ │ │ │ └── LanguageProvider/
│ │ │ │ └── index.js
│ │ │ ├── index.html
│ │ │ └── index.js
│ │ ├── pluginOrdering/
│ │ │ ├── .babelrc
│ │ │ └── messages.js
│ │ ├── resolution/
│ │ │ ├── .babelrc
│ │ │ └── messages.js
│ │ └── test.ts
│ ├── global.d.ts
│ ├── index.ts
│ └── test/
│ ├── fixtures/
│ │ ├── custom/
│ │ │ ├── a/
│ │ │ │ └── messages.js
│ │ │ ├── b/
│ │ │ │ └── messages.js
│ │ │ └── i18n.js
│ │ ├── default/
│ │ │ ├── a/
│ │ │ │ ├── App.js
│ │ │ │ └── messages.js
│ │ │ └── b/
│ │ │ └── messages.js
│ │ ├── removed/
│ │ │ ├── a/
│ │ │ │ └── messages.js
│ │ │ └── b/
│ │ │ └── messages.js
│ │ └── unsorted/
│ │ ├── a/
│ │ │ └── messages.js
│ │ └── b/
│ │ └── messages.js
│ ├── json/
│ │ ├── __snapshots__/
│ │ │ └── test.ts.snap
│ │ └── test.ts
│ ├── test.ts
│ └── yaml/
│ ├── __snapshots__/
│ │ └── test.ts.snap
│ └── test.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .all-contributorsrc
================================================
{
"projectName": "extract-react-intl-messages",
"projectOwner": "akameco",
"files": ["readme.md"],
"imageSize": 100,
"commit": true,
"contributors": [
{
"login": "akameco",
"name": "akameco",
"avatar_url": "https://avatars2.githubusercontent.com/u/4002137?v=4",
"profile": "http://akameco.github.io",
"contributions": ["code", "test", "doc", "infra"]
},
{
"login": "hoantran-it",
"name": "Hoan Tran",
"avatar_url": "https://avatars3.githubusercontent.com/u/13161875?v=4",
"profile": "http://hoantran.info",
"contributions": ["code", "test"]
},
{
"login": "giantpinkwalrus",
"name": "giantpinkwalrus",
"avatar_url": "https://avatars1.githubusercontent.com/u/3383240?v=4",
"profile": "https://github.com/giantpinkwalrus",
"contributions": ["code"]
},
{
"login": "enrique-ramirez",
"name": "enrique-ramirez",
"avatar_url": "https://avatars3.githubusercontent.com/u/1190640?v=4",
"profile": "https://github.com/enrique-ramirez",
"contributions": ["doc"]
},
{
"login": "hoschi",
"name": "Stefan Gojan",
"avatar_url": "https://avatars2.githubusercontent.com/u/163128?v=4",
"profile": "http://stefan-gojan.de",
"contributions": ["bug", "code", "test"]
},
{
"login": "solomon23",
"name": "Solomon English",
"avatar_url": "https://avatars1.githubusercontent.com/u/857744?v=4",
"profile": "https://lithe.net",
"contributions": ["code"]
},
{
"login": "Filson14",
"name": "Filip \"Filson\" Pasternak",
"avatar_url": "https://avatars1.githubusercontent.com/u/4540538?v=4",
"profile": "https://github.com/Filson14",
"contributions": ["code"]
},
{
"login": "nodaguti",
"name": "nodaguti",
"avatar_url": "https://avatars0.githubusercontent.com/u/27622?v=4",
"profile": "http://about.me/nodaguti",
"contributions": ["code", "test"]
},
{
"login": "fix-fix",
"name": "fix-fix",
"avatar_url": "https://avatars1.githubusercontent.com/u/11943024?v=4",
"profile": "https://github.com/fix-fix",
"contributions": ["code"]
},
{
"login": "bradbarrow",
"name": "bradbarrow",
"avatar_url": "https://avatars3.githubusercontent.com/u/1264276?v=4",
"profile": "http://bradbarrow.com",
"contributions": ["bug", "code", "test"]
},
{
"login": "gmaclennan",
"name": "Gregor MacLennan",
"avatar_url": "https://avatars1.githubusercontent.com/u/290457?v=4",
"profile": "http://ddem.us/",
"contributions": ["code"]
},
{
"login": "zarv1k",
"name": "Dmitry Zarva",
"avatar_url": "https://avatars1.githubusercontent.com/u/6296643?v=4",
"profile": "https://github.com/zarv1k",
"contributions": ["code"]
},
{
"login": "panpanc",
"name": "Michael Pan",
"avatar_url": "https://avatars2.githubusercontent.com/u/29132669?v=4",
"profile": "https://github.com/panpanc",
"contributions": ["example"]
},
{
"login": "testower",
"name": "Tom Erik Støwer",
"avatar_url": "https://avatars2.githubusercontent.com/u/231492?v=4",
"profile": "https://github.com/testower",
"contributions": ["code"]
},
{
"login": "lensbart",
"name": "Bart Lens",
"avatar_url": "https://avatars0.githubusercontent.com/u/20876627?v=4",
"profile": "https://nextbook.io",
"contributions": ["code"]
},
{
"login": "revskill10",
"name": "Truong Hoang Dung",
"avatar_url": "https://avatars3.githubusercontent.com/u/1390196?v=4",
"profile": "https://github.com/revskill10",
"contributions": ["example"]
},
{
"login": "Nestoro",
"name": "Nestoro",
"avatar_url": "https://avatars.githubusercontent.com/u/13397845?v=4",
"profile": "https://github.com/Nestoro",
"contributions": ["code"]
},
{
"login": "lightnet328",
"name": "Yutaro Kido",
"avatar_url": "https://avatars.githubusercontent.com/u/2351326?v=4",
"profile": "https://lightnet328.com/",
"contributions": ["code"]
}
],
"repoType": "github",
"commitConvention": "none",
"repoHost": "https://github.com",
"skipCi": true,
"contributorsPerLine": 7
}
================================================
FILE: .babelrc
================================================
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
],
"@babel/preset-react"
]
}
================================================
FILE: .editorconfig
================================================
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
================================================
FILE: .eslintrc
================================================
{
"extends": ["precure/auto"],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off"
}
}
================================================
FILE: .gitattributes
================================================
* text=auto
*.js text eol=lf
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
<!--
Thanks for your interest in the project.
I appreciate bugs filed and PRs submitted!
I'll probably ask you to submit the fix (after giving some direction).
English/日本語(日本語で入力して大丈夫です。日本語の方が迅速です)
-->
- version:
- `node` version:
- `npm` (or `yarn`) version:
**Do you want to request a *feature* or report a *bug*?:**
**What is the current behavior?:**
**What is the expected behavior?:**
**Suggested solution:**
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
Thanks for your interest in the project. I appreciate bugs filed and PRs submitted!
English/日本語(日本語で入力して大丈夫です。日本語の方が迅速です)
-->
<!-- What changes are being made? (What feature/bug is being fixed here?) / 何が変更されていますか?-->
**What**:
<!-- Why are these changes necessary? / なぜその変更をする必要がありましたか?-->
**Why**:
<!-- How were these changes implemented? / これらの変更をどのように実装しましたか?-->
**How**:
**Checklist**:
<!-- add "N/A" to the end of each line that's irrelevant to your changes to check an item, place an "x" in the box like so: "- [x] Documentation" -->
* [ ] Documentation
* [ ] Tests
* [ ] Ready to be merged <!-- In your opinion, is this ready to be merged as soon as it's reviewed? -->
<!-- feel free to add additional comments. -->
================================================
FILE: .github/workflows/test.yml
================================================
name: test
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
# LTS (20.x) and latest (22.x) only
node-version: [20.x, 22.x]
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: npm install
- run: npm run lint
- run: npm run test
================================================
FILE: .gitignore
================================================
# Dependencies
node_modules/
# Build outputs
lib/
dist/
compiled/
# Cache files
.eslintcache
.test-cache
# OS files
.DS_Store
Thumbs.db
# IDE files
.vscode/
.idea/
*.swp
*.swo
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
.nyc_output/
================================================
FILE: .husky/pre-commit
================================================
npx lint-staged
================================================
FILE: .prettierignore
================================================
**/test/fixtures/**
.github
dist
package.json
================================================
FILE: .prettierrc
================================================
{
"semi": false,
"singleQuote": true,
"trailingComma": "none"
}
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at akameco.t@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: eslint.config.mjs
================================================
// ESLint v9+ config
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ['src/**/*.ts'],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module'
}
}
},
{
ignores: ['dist/', 'node_modules/', 'compiled/']
}
]
================================================
FILE: example/basic/babel.config.js
================================================
module.exports = function (api) {
api.cache(true)
return {
presets: ['react-app']
}
}
================================================
FILE: example/basic/i18n/.keep
================================================
================================================
FILE: example/basic/i18n/en.json
================================================
{
"App": {
"hello": "Hello Button",
"submit": "Submit Button"
},
"a": {
"hello": "hello",
"world": "world"
}
}
================================================
FILE: example/basic/i18n/ja.json
================================================
{
"App": {
"hello": "",
"submit": ""
},
"a": {
"hello": "",
"world": ""
}
}
================================================
FILE: example/basic/package.json
================================================
{
"name": "basic",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"i18n": "NODE_ENV=development extract-messages -l=en,ja -o i18n -d en 'src/**/*.{js,jsx}'"
},
"dependencies": {
"react": "^16.13.1",
"react-intl": "^4.3.1"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"babel-preset-react-app": "^9.1.2",
"extract-react-intl-messages": "latest"
}
}
================================================
FILE: example/basic/src/App.jsx
================================================
import React from 'react'
import { injectIntl, useIntl } from 'react-intl'
export const SubmitButton = injectIntl(({ intl }) => {
const label = intl.formatMessage({
id: 'App.submit',
defaultMessage: 'Submit Button'
})
return <button aria-label={label}>{label}</button>
})
export const HelloButton = () => {
const intl = useIntl()
const label = intl.formatMessage({
id: 'App.hello',
defaultMessage: 'Hello Button'
})
return <button aria-label={label}>{label}</button>
}
================================================
FILE: example/basic/src/messages.js
================================================
/* eslint-disable import/no-extraneous-dependencies */
import { defineMessages } from 'react-intl'
export default defineMessages({
hello: {
id: 'a.hello',
defaultMessage: 'hello'
},
world: {
id: 'a.world',
defaultMessage: 'world'
}
})
================================================
FILE: example/with-typescript/babel.config.js
================================================
module.exports = function (api) {
api.cache(true)
return {
presets: ['@babel/preset-react', '@babel/preset-typescript']
}
}
================================================
FILE: example/with-typescript/i18n/en.json
================================================
{
"App": {
"hello": "Hello Button",
"submit": "Submit Button"
},
"a": {
"hello": "hello",
"world": "world"
}
}
================================================
FILE: example/with-typescript/i18n/ja.json
================================================
{
"App": {
"hello": "",
"submit": ""
},
"a": {
"hello": "",
"world": ""
}
}
================================================
FILE: example/with-typescript/package.json
================================================
{
"name": "with-typescript",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"i18n": "extract-messages -l=en,ja -o i18n -d en 'src/**/*.{ts,tsx}'"
},
"dependencies": {
"react": "^16.13.1",
"react-intl": "^4.3.1"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/preset-react": "^7.9.4",
"@babel/preset-typescript": "^7.9.0",
"extract-react-intl-messages": "latest",
"typescript": "^3.8.3"
}
}
================================================
FILE: example/with-typescript/src/App.tsx
================================================
import React from 'react'
import { injectIntl, useIntl } from 'react-intl'
export const SubmitButton = injectIntl(({ intl }) => {
const label = intl.formatMessage({
id: 'App.submit',
defaultMessage: 'Submit Button'
})
return <button aria-label={label}>{label}</button>
})
export const HelloButton = () => {
const intl = useIntl()
const label = intl.formatMessage({
id: 'App.hello',
defaultMessage: 'Hello Button'
})
return <button aria-label={label}>{label}</button>
}
================================================
FILE: example/with-typescript/src/messages.ts
================================================
import { defineMessages } from 'react-intl'
export default defineMessages({
hello: {
id: 'a.hello',
defaultMessage: 'hello'
},
world: {
id: 'a.world',
defaultMessage: 'world'
}
})
================================================
FILE: jest.config.js
================================================
export default {
testPathIgnorePatterns: [
'<rootDir>[/\\\\](dist|compiled|node_modules)[/\\\\]'
],
testEnvironment: 'node',
preset: 'ts-jest',
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1'
},
transform: {
'^.+\\.ts$': [
'ts-jest',
{
useESM: true,
tsconfig: {
module: 'esnext'
}
}
]
},
transformIgnorePatterns: ['node_modules/(?!(.*\\.mjs$))']
}
================================================
FILE: license
================================================
The MIT License (MIT)
Copyright (c) akameco <akameco.t@gmail.com> (akameco.github.io)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: package.json
================================================
{
"name": "extract-react-intl-messages",
"version": "5.0.0",
"description": "Extract react-intl messages",
"license": "MIT",
"repository": "akameco/extract-react-intl-messages",
"type": "module",
"author": {
"name": "akameco",
"email": "akameco.t@gmail.com",
"url": "https://akameco.github.io"
},
"engines": {
"node": ">=20"
},
"main": "dist/index.js",
"scripts": {
"fmt": "prettier --write .",
"example": "./cli.js -l=en,ja -o example/i18n -d en 'example/**/*.{js,tsx}'",
"example:yaml": "./cli.js -l=en,ja -f=yaml -o example/i18n -d en 'example/**/*.{js,tsx}'",
"prepublish": "npm run build",
"build": "tsc",
"lint": "eslint src/**/*.ts --fix --cache",
"pretest": "node -e \"import('fs/promises').then(fs => fs.rm('.test-cache', {recursive: true, force: true})).catch(() => {})\"",
"test": "jest",
"posttest": "node -e \"import('fs/promises').then(fs => fs.rm('.test-cache', {recursive: true, force: true})).catch(() => {})\"",
"prepare": "husky"
},
"bin": {
"extract-messages": "dist/cli.js",
"extract-react-intl-messages": "dist/cli.js"
},
"files": [
"dist"
],
"keywords": [
"react",
"i18n",
"intl",
"react-intl",
"extract",
"json",
"messages"
],
"dependencies": {
"@babel/core": "^7.28.3",
"babel-plugin-react-intl": "^7.9.4",
"deepmerge": "^4.3.1",
"file-entry-cache": "^5.0.1",
"flat": "^5.0.2",
"glob": "11.0.3",
"js-yaml": "4.1.0",
"load-json-file": "^6.2.0",
"lodash.merge": "^4.6.2",
"meow": "13.2.0",
"read-babelrc-up": "^1.1.0",
"sort-keys": "^4.2.0",
"write-json-file": "^4.3.0"
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-env": "^7.28.3",
"@babel/preset-flow": "^7.27.1",
"@babel/preset-react": "^7.27.1",
"@types/file-entry-cache": "^5.0.4",
"@types/flat": "^5.0.5",
"@types/js-yaml": "^3.12.10",
"@types/load-json-file": "^2.0.7",
"@types/lodash.merge": "^4.6.9",
"@types/node": "^24.3.0",
"@types/temp-write": "^4.0.0",
"@types/write-json-file": "^2.2.1",
"babel-plugin-react-intl-auto": "^3.3.0",
"eslint": "^9.34.0",
"husky": "9.1.7",
"jest": "^30.1.1",
"lint-staged": "16.1.5",
"prettier": "3.6.2",
"temp-write": "^4.0.0",
"tempy": "^0.5.0",
"ts-jest": "^29.4.1",
"typescript": "^5.9.2",
"typescript-eslint": "^8.41.0"
},
"lint-staged": {
"*.ts": [
"prettier --write",
"eslint --fix"
],
"*.{js,json,md}": [
"prettier --write"
]
}
}
================================================
FILE: readme.md
================================================
# extract-react-intl-messages
[](https://github.com/akameco/extract-react-intl-messages/actions?query=workflow%3Atest)
[](https://github.com/facebook/jest)
[](https://github.com/prettier/prettier)
[](./license)
[](#contributors-)
This package will generate json or yaml files from a glob. It will generate one file per locale, with the ids of each message defined by the [`defineMessages`](https://github.com/yahoo/react-intl/wiki/API#definemessages) function of [react-intl](https://github.com/yahoo/react-intl). The value of each of these keys will be an empty string, except for your `defaultLocale` which will be populated with the [`defaultMessage`](https://github.com/yahoo/react-intl/wiki/API#message-descriptor).
## Dependencies
### Babel
- 0.x works with Babel 6
## Install
```
$ npm install --save-dev extract-react-intl-messages
```
## Usage
app/components/App/messages.js
```js
import { defineMessages, useIntl } from 'react-intl'
export default defineMessages({
hello: {
id: 'a.hello',
defaultMessage: 'hello'
},
world: {
id: 'a.world',
defaultMessage: 'world'
}
})
export const SubmitButton = () => {
const intl = useIntl()
const label = intl.formatMessage({
id: 'a.submit',
defaultMessage: 'Submit Button'
})
return <button aria-label={label}>{label}</button>
}
```
### Run Script
```
$ extract-messages -l=en,ja -o app/translations -d en --flat false './app/**/!(*.test).js'
```
### Output
app/translations/en.json
```json
{
"a": {
"hello": "hello",
"world": "world",
"submit": "Submit Button"
}
}
```
app/translations/ja.json
```json
{
"a": {
"hello": "",
"world": "",
"submit": ""
}
}
```
## Recommend
Use with [babel-plugin-react-intl-auto: i18n for the component age. Auto management react-intl ID.](https://github.com/akameco/babel-plugin-react-intl-auto)
## CLI
```console
$ extract-messages --help
Extract react-intl messages
Usage
$ extract-react-intl-messages <input>
$ extract-messages <input>
Options
-o, --output Output directory [require: true]
-l, --locales locales [require: true]
-f, --format json | yaml [default: json]
-d, --defaultLocale default locale
--overwriteDefault default: true
--flat json [default: true] | yaml [default: false]
--indent default: 2
Example
$ extract-messages --locales=ja,en --output app/translations 'app/**/*.js'
$ extract-messages -l=ja,en -o i18n 'src/**/*.js'
$ extract-messages -l=ja,en -o app/translations -f yaml 'app/**/messages.js'
```
### create-react-app user
create `.babelrc` like this.
```json
{
"presets": ["react-app"]
}
```
Run with `NODE_ENV=development`.
```
$ NODE_ENV=development extract-messages ...
```
### TypeScript
babel required.
See [example/with-typescript](example/with-typescript)
```
npm install --save-dev @babel/core @babel/preset-typescript @babel/preset-react
```
`babel.config.js`
```js
module.exports = function (api) {
api.cache(true)
return {
presets: ['@babel/preset-react', '@babel/preset-typescript']
}
}
```
## API
### extractReactIntlMessages(locales, input, buildDir, [options])
#### locales
Type: `Array<string>`
Example: `['en', 'ja']`
#### input
Type: `Array<string>`
Target files.
glob.
#### buildDir
Type: `string`
Export directory.
#### options
##### defaultLocale
Type: `string`<br>
Default: `en`
##### format
Type: `json` | `yaml`<br>
Default: `json`
Set extension to output.
##### overwriteDefault
Type: `boolean`<br>
Default: true
If overwriteDefault is `false`, it will not overwrite messages in the default locale.
##### indent
Type: `number`<br>
Default: `2`
##### flat
Type: `boolean`<br>
Default: `true`
If format is `yaml`, set to `false`.
Be careful if `false`.
See [this issue](https://github.com/akameco/extract-react-intl-messages/issues/3).
##### babel-plugin-react-intl's Options
See https://github.com/formatjs/formatjs/tree/master/packages/babel-plugin-react-intl#options
## Contributors
Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://akameco.github.io"><img src="https://avatars2.githubusercontent.com/u/4002137?v=4?s=100" width="100px;" alt="akameco"/><br /><sub><b>akameco</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/commits?author=akameco" title="Code">💻</a> <a href="https://github.com/akameco/extract-react-intl-messages/commits?author=akameco" title="Tests">⚠️</a> <a href="https://github.com/akameco/extract-react-intl-messages/commits?author=akameco" title="Documentation">📖</a> <a href="#infra-akameco" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hoantran.info"><img src="https://avatars3.githubusercontent.com/u/13161875?v=4?s=100" width="100px;" alt="Hoan Tran"/><br /><sub><b>Hoan Tran</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/commits?author=hoantran-it" title="Code">💻</a> <a href="https://github.com/akameco/extract-react-intl-messages/commits?author=hoantran-it" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/giantpinkwalrus"><img src="https://avatars1.githubusercontent.com/u/3383240?v=4?s=100" width="100px;" alt="giantpinkwalrus"/><br /><sub><b>giantpinkwalrus</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/commits?author=giantpinkwalrus" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/enrique-ramirez"><img src="https://avatars3.githubusercontent.com/u/1190640?v=4?s=100" width="100px;" alt="enrique-ramirez"/><br /><sub><b>enrique-ramirez</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/commits?author=enrique-ramirez" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://stefan-gojan.de"><img src="https://avatars2.githubusercontent.com/u/163128?v=4?s=100" width="100px;" alt="Stefan Gojan"/><br /><sub><b>Stefan Gojan</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/issues?q=author%3Ahoschi" title="Bug reports">🐛</a> <a href="https://github.com/akameco/extract-react-intl-messages/commits?author=hoschi" title="Code">💻</a> <a href="https://github.com/akameco/extract-react-intl-messages/commits?author=hoschi" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://lithe.net"><img src="https://avatars1.githubusercontent.com/u/857744?v=4?s=100" width="100px;" alt="Solomon English"/><br /><sub><b>Solomon English</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/commits?author=solomon23" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Filson14"><img src="https://avatars1.githubusercontent.com/u/4540538?v=4?s=100" width="100px;" alt="Filip "Filson" Pasternak"/><br /><sub><b>Filip "Filson" Pasternak</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/commits?author=Filson14" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://about.me/nodaguti"><img src="https://avatars0.githubusercontent.com/u/27622?v=4?s=100" width="100px;" alt="nodaguti"/><br /><sub><b>nodaguti</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/commits?author=nodaguti" title="Code">💻</a> <a href="https://github.com/akameco/extract-react-intl-messages/commits?author=nodaguti" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fix-fix"><img src="https://avatars1.githubusercontent.com/u/11943024?v=4?s=100" width="100px;" alt="fix-fix"/><br /><sub><b>fix-fix</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/commits?author=fix-fix" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://bradbarrow.com"><img src="https://avatars3.githubusercontent.com/u/1264276?v=4?s=100" width="100px;" alt="bradbarrow"/><br /><sub><b>bradbarrow</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/issues?q=author%3Abradbarrow" title="Bug reports">🐛</a> <a href="https://github.com/akameco/extract-react-intl-messages/commits?author=bradbarrow" title="Code">💻</a> <a href="https://github.com/akameco/extract-react-intl-messages/commits?author=bradbarrow" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://ddem.us/"><img src="https://avatars1.githubusercontent.com/u/290457?v=4?s=100" width="100px;" alt="Gregor MacLennan"/><br /><sub><b>Gregor MacLennan</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/commits?author=gmaclennan" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zarv1k"><img src="https://avatars1.githubusercontent.com/u/6296643?v=4?s=100" width="100px;" alt="Dmitry Zarva"/><br /><sub><b>Dmitry Zarva</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/commits?author=zarv1k" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/panpanc"><img src="https://avatars2.githubusercontent.com/u/29132669?v=4?s=100" width="100px;" alt="Michael Pan"/><br /><sub><b>Michael Pan</b></sub></a><br /><a href="#example-panpanc" title="Examples">💡</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/testower"><img src="https://avatars2.githubusercontent.com/u/231492?v=4?s=100" width="100px;" alt="Tom Erik Støwer"/><br /><sub><b>Tom Erik Støwer</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/commits?author=testower" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://nextbook.io"><img src="https://avatars0.githubusercontent.com/u/20876627?v=4?s=100" width="100px;" alt="Bart Lens"/><br /><sub><b>Bart Lens</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/commits?author=lensbart" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/revskill10"><img src="https://avatars3.githubusercontent.com/u/1390196?v=4?s=100" width="100px;" alt="Truong Hoang Dung"/><br /><sub><b>Truong Hoang Dung</b></sub></a><br /><a href="#example-revskill10" title="Examples">💡</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Nestoro"><img src="https://avatars.githubusercontent.com/u/13397845?v=4?s=100" width="100px;" alt="Nestoro"/><br /><sub><b>Nestoro</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/commits?author=Nestoro" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://lightnet328.com/"><img src="https://avatars.githubusercontent.com/u/2351326?v=4?s=100" width="100px;" alt="Yutaro Kido"/><br /><sub><b>Yutaro Kido</b></sub></a><br /><a href="https://github.com/akameco/extract-react-intl-messages/commits?author=lightnet328" title="Code">💻</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
## License
MIT © [akameco](http://akameco.github.io)
================================================
FILE: src/cli.ts
================================================
#!/usr/bin/env node
import meow from 'meow'
import extractMessage from './index.js'
const cli = meow(
`
Usage
$ extract-react-intl-messages <input>
$ extract-messages <input>
Options
-o, --output Output directory [require: true]
-l, --locales locales [require: true]
-f, --format json | yaml [default: json]
-d, --default-locale default locale
--overwriteDefault [default: true]
--flat json [default: true] | yaml [default: false]
--cache [default: false]
--cacheLocation [default: .extract-react-intl-messages-cache]
--indent default: 2
Example
$ extract-messages --locales=ja,en --output app/translations 'app/**/*.js'
$ extract-messages -l=ja,en -o app/translations -f yaml 'app/**/messages.js'
`,
{
importMeta: import.meta,
flags: {
flat: {
type: 'boolean'
},
output: {
type: 'string',
shortFlag: 'o'
},
locales: {
type: 'string',
shortFlag: 'l'
},
format: {
type: 'string',
shortFlag: 'f',
default: 'json'
},
defaultLocale: {
type: 'string',
shortFlag: 'd'
},
overwriteDefault: {
type: 'boolean',
default: true
},
withDescriptions: {
type: 'boolean',
default: false
},
// babel-plugin-react-intl boolean options
extractSourceLocation: {
type: 'boolean',
default: false
},
removeDefaultMessage: {
type: 'boolean'
},
extractFromFormatMessageCall: {
type: 'boolean',
default: true
},
indent: {
type: 'number',
default: 2
}
}
}
)
const { output, locales } = cli.flags
if (!output) {
console.log('ERROR: required output')
process.exit(1)
}
if (!locales || typeof locales !== 'string') {
console.log('ERROR: required locales')
process.exit(1)
}
const localesMap = locales.split(',')
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
extractMessage(localesMap, cli.input[0], output, cli.flags as any)
================================================
FILE: src/extract-react-intl/index.ts
================================================
import path from 'path'
import { glob } from 'glob'
import { promisify } from 'util'
import merge from 'lodash.merge'
import deepmerge from 'deepmerge'
import {
resolvePlugin,
resolvePreset,
transformFile,
PluginItem
} from '@babel/core'
import readBabelrcUp from 'read-babelrc-up'
import babelPluginReactIntl from 'babel-plugin-react-intl'
import fileEntryCache, { FileDescriptor } from 'file-entry-cache'
type LocaleMap = Record<string, Record<string, unknown>>
const localeMap = (arr: string[]): LocaleMap =>
arr.reduce((obj: LocaleMap, x: string) => {
obj[x] = {}
return obj
}, {})
const createResolveList =
(fn: (name: string, dirname: string) => string | null) =>
(list: PluginItem[], cwd: string) =>
list.map((x) => (typeof x === 'string' ? fn(x, cwd) : x))
const resolvePresets = createResolveList(resolvePreset)
const resolvePlugins = createResolveList(resolvePlugin)
const getBabelrc = (cwd: string) => {
try {
const babelrc = readBabelrcUp.sync({ cwd }).babel
if (!babelrc.env) {
return babelrc
}
const env = process.env.BABEL_ENV || process.env.NODE_ENV || 'development'
return deepmerge(babelrc, babelrc.env[env] || {})
} catch {
return { presets: [], plugins: [] }
}
}
const getBabelrcDir = (cwd: string) =>
path.dirname(readBabelrcUp.sync({ cwd }).path)
const babelPluginReactIntlOptions = new Set([
'moduleSourceName',
'extractSourceLocation',
'messagesDir',
'overrideIdFn',
'removeDefaultMessage',
'extractFromFormatMessageCall',
'additionalComponentNames'
])
type Options = {
[key: string]: unknown
defaultLocale?: string
cwd?: string
cache?: boolean
cacheLocation?: string
withDescriptions?: boolean
}
type Message = {
id: string
defaultMessage: string
description: string
}
type CacheData = {
defaultLocale: string
localeMap: LocaleMap
}
type File = FileDescriptor & {
meta: {
data: CacheData
}
}
export default async (
locales: string[],
pattern: string,
{
defaultLocale = 'en',
withDescriptions = false,
cwd = process.cwd(),
cache: cacheEnabled = false,
cacheLocation = '.extract-react-intl-messages-cache',
...pluginOptions
}: Options = {}
) => {
if (!Array.isArray(locales)) {
throw new TypeError(`Expected a Array, got ${typeof locales}`)
}
if (typeof pattern !== 'string') {
throw new TypeError(`Expected a string, got ${typeof pattern}`)
}
const babelrc = getBabelrc(cwd) || {}
const babelrcDir = getBabelrcDir(cwd)
const presets = babelrc.presets || []
const plugins = babelrc.plugins || []
if (
!plugins.find(
(plugin: PluginItem) =>
(Array.isArray(plugin) ? plugin[0] : plugin) === 'react-intl'
)
) {
// Append a the `react-intl` babel plugin only when it isn’t already included in the babel config
presets.unshift({
plugins: [
[
babelPluginReactIntl,
Object.entries(pluginOptions).reduce((acc, [key, value]) => {
if (babelPluginReactIntlOptions.has(key)) {
return { ...acc, [key]: value }
}
return acc
}, {})
]
]
})
}
const files: string[] = await glob(pattern)
if (files.length === 0) {
throw new Error(`File not found (${pattern})`)
}
const cachePath = path.resolve(cacheLocation)
const cacheDirname = path.dirname(cachePath)
const cacheBasename = path.basename(cachePath)
const cache = cacheEnabled
? fileEntryCache.create(cacheBasename, cacheDirname)
: null
const extractFromFile = async (file: string) => {
const babelOpts = {
presets: resolvePresets(presets, babelrcDir),
plugins: resolvePlugins(plugins, babelrcDir)
}
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const transformResult = await promisify(transformFile as any)(
file,
babelOpts
)
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const { metadata } = transformResult as { metadata: any }
const localeObj = localeMap(locales)
const result = metadata['react-intl'].messages as Message[]
for (const { id, defaultMessage, description } of result) {
for (const locale of locales) {
const message = defaultLocale === locale ? defaultMessage : ''
localeObj[locale][id] = withDescriptions
? { message, description }
: message
}
}
return localeObj
}
const extractFromCache = async (file: string) => {
if (cache === null) {
return extractFromFile(file)
}
const cachedLocaleObj = cache?.getFileDescriptor(file) as File | undefined
const changed = cachedLocaleObj?.changed
const data = cachedLocaleObj?.meta.data
if (changed === false && data?.defaultLocale === defaultLocale) {
return data.localeMap
}
const localeObj = await extractFromFile(file)
if (cachedLocaleObj) {
cachedLocaleObj.meta.data = { defaultLocale, localeMap: localeObj }
}
return localeObj
}
const extract = cacheEnabled ? extractFromCache : extractFromFile
const arr = await Promise.all(files.map(extract))
if (cache) {
cache.reconcile()
}
return arr.reduce((h, obj) => merge(h, obj), localeMap(locales))
}
================================================
FILE: src/extract-react-intl/readme.md
================================================
# extract-react-intl
## API
### extractReactIntl(locales, pattern, [options])
Return a `Promise` wrapped extracted messages.
#### locales
Type: `Array<string>`
Example: `['en', 'ja']`
#### pattern
Type: `string`
File path with glob.
#### options
Additional options.
#### defaultLocale
Type: `string`<br> Default: `en`
Set default locale for your app.
##### cwd
Type: `string`<br> Default: `.`
**You most likely don't need this.**
Change run path.
================================================
FILE: src/extract-react-intl/test/__snapshots__/test.ts.snap
================================================
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`babel plugin execution order 1`] = `
{
"en": {
"src.extract-react-intl.test.pluginOrdering.test": "auto",
},
}
`;
exports[`extract from file 1`] = `
{
"en": {
"components.App.1248161314": "Submit button",
"components.App.hello": "hello",
"components.App.world": "world",
"components/Greeting/welcome": "
Welcome {name}, you have received {unreadCount, plural,
=0 {no new messages}
one {{formattedUnreadCount} new message}
other {{formattedUnreadCount} new messages}
} since {formattedLastLoginTime}.
",
},
"ja": {
"components.App.1248161314": "",
"components.App.hello": "",
"components.App.world": "",
"components/Greeting/welcome": "",
},
}
`;
exports[`extract from file by enabling cache and extract from cache 1`] = `
{
"en": {
"components.App.1248161314": "Submit button",
"components.App.hello": "hello",
"components.App.world": "world",
"components/Greeting/welcome": "
Welcome {name}, you have received {unreadCount, plural,
=0 {no new messages}
one {{formattedUnreadCount} new message}
other {{formattedUnreadCount} new messages}
} since {formattedLastLoginTime}.
",
},
"ja": {
"components.App.1248161314": "",
"components.App.hello": "",
"components.App.world": "",
"components/Greeting/welcome": "",
},
}
`;
exports[`extract from file by enabling cache and extract from cache 2`] = `
{
"en": {
"components.App.1248161314": "Submit button",
"components.App.hello": "hello",
"components.App.world": "world",
"components/Greeting/welcome": "
Welcome {name}, you have received {unreadCount, plural,
=0 {no new messages}
one {{formattedUnreadCount} new message}
other {{formattedUnreadCount} new messages}
} since {formattedLastLoginTime}.
",
},
"ja": {
"components.App.1248161314": "",
"components.App.hello": "",
"components.App.world": "",
"components/Greeting/welcome": "",
},
}
`;
exports[`extract from file with descriptions 1`] = `
{
"en": {
"components.App.hello": {
"description": "hello message description",
"message": "hello",
},
"components.App.world": {
"description": "world message description",
"message": "world",
},
"components/Greeting/welcome": {
"description": "Welcome message description",
"message": "
Welcome {name}, you have received {unreadCount, plural,
=0 {no new messages}
one {{formattedUnreadCount} new message}
other {{formattedUnreadCount} new messages}
} since {formattedLastLoginTime}.
",
},
},
"ja": {
"components.App.hello": {
"description": "hello message description",
"message": "",
},
"components.App.world": {
"description": "world message description",
"message": "",
},
"components/Greeting/welcome": {
"description": "Welcome message description",
"message": "",
},
},
}
`;
================================================
FILE: src/extract-react-intl/test/fixtures/.babelrc
================================================
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": [
"last 2 versions",
"safari >= 7"
]
}
}
],
"@babel/preset-react",
"@babel/preset-flow"
],
"plugins": [
"@babel/plugin-proposal-class-properties"
],
"env": {
"react-intl": {
"plugins": [
[
"react-intl-auto",
{
"removePrefix": "src.extract-react-intl.test.fixtures"
}
]
]
}
}
}
================================================
FILE: src/extract-react-intl/test/fixtures/components/App/index.js
================================================
// @flow
import React, { Component } from 'react'
import { FormattedMessage, injectIntl } from 'react-intl'
import Greeting from '../Greeting'
import messages from './messages'
injectIntl(({ intl }) => {
const label = intl.formatMessage({ defaultMessage: "Submit button" })
return <button aria-label={label}>{label}</button>
});
export default class App extends Component {
render() {
const user = {
name: 'Eric',
unreadCount: 4,
lastLoginTime: Date.now() - 1000 * 60 * 60 * 24
}
return (
<div>
<FormattedMessage {...messages.hello} />
<Greeting user={user} />
</div>
)
}
}
================================================
FILE: src/extract-react-intl/test/fixtures/components/App/messages.js
================================================
// @flow
import { defineMessages } from 'react-intl'
export default defineMessages({
// hello message description
hello: 'hello',
// world message description
world: 'world'
})
================================================
FILE: src/extract-react-intl/test/fixtures/components/Greeting/index.js
================================================
// @flow
import React, { Component } from 'react'
import {
FormattedMessage,
FormattedNumber,
FormattedRelative
} from 'react-intl'
import messages from './messages'
type Props = {
user: {
name: string,
unreadCount: number,
lastLoginTime: number
}
}
export default class Greeting extends Component {
props: Props
render() {
const { user } = this.props
return (
<p>
<FormattedMessage
{...messages.welcome}
values={{
name: <b>{user.name}</b>,
unreadCount: user.unreadCount,
formattedUnreadCount: (
<b>
<FormattedNumber value={user.unreadCount} />
</b>
),
formattedLastLoginTime: (
<FormattedRelative value={user.lastLoginTime} />
)
}}
/>
</p>
)
}
}
function defaultMessage() {
return 'hello'
}
================================================
FILE: src/extract-react-intl/test/fixtures/components/Greeting/messages.js
================================================
// @flow
import { defineMessages } from 'react-intl'
export default defineMessages({
// Welcome message description
welcome: {
id: 'components/Greeting/welcome',
defaultMessage: `
Welcome {name}, you have received {unreadCount, plural,
=0 {no new messages}
one {{formattedUnreadCount} new message}
other {{formattedUnreadCount} new messages}
} since {formattedLastLoginTime}.
`
}
})
================================================
FILE: src/extract-react-intl/test/fixtures/components/LanguageProvider/index.js
================================================
// @flow
import React, { Component } from 'react'
import { addLocaleData, IntlProvider } from 'react-intl'
import enLocaleData from 'react-intl/locale-data/en'
import jaLocaleData from 'react-intl/locale-data/ja'
import enMessages from '../../translations/en.json'
import jaMessages from '../../translations/ja.json'
addLocaleData(enLocaleData)
addLocaleData(jaLocaleData)
const messages = {
en: enMessages,
ja: jaMessages
}
export default class LanguageProvider extends Component {
props: { children?: React$Element<*> }
state: { locale: string } = { locale: 'en' }
render() {
const { locale } = this.state
return (
<div>
<IntlProvider locale={locale} messages={messages[locale]}>
{this.props.children}
</IntlProvider>
<a onClick={() => this.setState({ locale: 'en' })}>English</a>
/
<a onClick={() => this.setState({ locale: 'ja' })}>日本語</a>
</div>
)
}
}
================================================
FILE: src/extract-react-intl/test/fixtures/index.html
================================================
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Example</title>
</head>
<body>
<div id="root"></div>
<script src="/assets/bundle.js"></script>
</body>
</html>
================================================
FILE: src/extract-react-intl/test/fixtures/index.js
================================================
// @flow
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App'
import LanguageProvider from './components/LanguageProvider'
ReactDOM.render(
<LanguageProvider>
<App />
</LanguageProvider>,
document.getElementById('root')
)
================================================
FILE: src/extract-react-intl/test/pluginOrdering/.babelrc
================================================
{
"presets": ["@babel/preset-flow"],
"plugins": ["react-intl-auto"]
}
================================================
FILE: src/extract-react-intl/test/pluginOrdering/messages.js
================================================
import { defineMessages } from 'react-intl'
export default defineMessages({
test: 'auto'
})
================================================
FILE: src/extract-react-intl/test/resolution/.babelrc
================================================
{
"presets": ["@babel/preset-flow"],
"plugins": ["react-intl"]
}
================================================
FILE: src/extract-react-intl/test/resolution/messages.js
================================================
import { defineMessages } from 'react-intl'
export default defineMessages({
test: {
id: 'test',
defaultMessage: 'test'
}
})
================================================
FILE: src/extract-react-intl/test/test.ts
================================================
import m from '..'
const pattern = 'src/extract-react-intl/test/fixtures/**/*.js'
const locales = ['en', 'ja']
test('extract from file', async () => {
process.env.BABEL_ENV = 'react-intl'
const x = await m(locales, pattern, {
defaultLocale: 'en',
cwd: `${__dirname}/fixtures`,
extractFromFormatMessageCall: true
})
expect(x).toMatchSnapshot()
})
test('extract from file by enabling cache and extract from cache', async () => {
process.env.BABEL_ENV = 'react-intl'
const x = await m(locales, pattern, {
defaultLocale: 'en',
cwd: `${__dirname}/fixtures`,
extractFromFormatMessageCall: true,
cache: true,
cacheLocation: '.test-cache'
})
expect(x).toMatchSnapshot()
const y = await m(locales, pattern, {
defaultLocale: 'en',
cwd: `${__dirname}/fixtures`,
extractFromFormatMessageCall: true,
cache: true,
cacheLocation: '.test-cache'
})
expect(y).toMatchSnapshot()
})
// TODO: fix
test.skip('babelrc path resolution', async () => {
const x = await m(['en'], './extract-react-intl/test/resolution/**/*.js', {
defaultLocale: 'en',
cwd: `${__dirname}/resolution`
})
expect(x).toMatchSnapshot()
})
test('babel plugin execution order', async () => {
const x = await m(
['en'],
'src/extract-react-intl/test/pluginOrdering/**/*.js',
{ defaultLocale: 'en', cwd: `${__dirname}/pluginOrdering` }
)
expect(x).toMatchSnapshot()
})
test('error', async () => {
expect.assertions(1)
await m(locales, 'notfound', {
defaultLocale: 'en',
cwd: `${__dirname}/fixtures`
}).catch((error) => {
expect(error.message).toMatch('File not found')
})
})
test('extract from file with descriptions', async () => {
process.env.BABEL_ENV = 'react-intl'
const x = await m(locales, pattern, {
defaultLocale: 'en',
cwd: './test/fixtures',
withDescriptions: true
})
expect(x).toMatchSnapshot()
})
================================================
FILE: src/global.d.ts
================================================
declare module 'read-babelrc-up' {
function sync(opts: { cwd: string }): {
path: string
babel: import('@babel/core').TransformOptions
}
}
================================================
FILE: src/index.ts
================================================
import path from 'path'
import fs from 'fs'
import yaml from 'js-yaml'
import { promisify } from 'util'
import { flatten, unflatten } from 'flat'
import loadJsonFile from 'load-json-file'
import writeJsonFile from 'write-json-file'
import sortKeys from 'sort-keys'
import _extractReactIntl from './extract-react-intl/index.js'
const writeJson = (outputPath: string, obj: object, indent: number) => {
return writeJsonFile(`${outputPath}.json`, obj, { indent })
}
const writeYaml = (outputPath: string, obj: object, indent: number) => {
return promisify(fs.writeFile)(
`${outputPath}.yml`,
yaml.dump(obj, { indent }),
'utf8'
)
}
const isJson = (ext: string) => ext === 'json'
function loadLocaleFiles(locales: string[], buildDir: string, ext: string) {
const oldLocaleMaps: Record<string, Record<string, object>> = {}
try {
fs.mkdirSync(buildDir, { recursive: true })
} catch {
// Directory already exists or other error, continue
}
for (const locale of locales) {
const file = path.resolve(buildDir, `${locale}.${ext}`)
// Initialize json file
try {
const output = isJson(ext) ? JSON.stringify({}) : yaml.dump({})
fs.writeFileSync(file, output, { flag: 'wx' })
} catch (error: unknown) {
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {
throw error
}
}
let messages = isJson(ext)
? loadJsonFile.sync(file)
: yaml.load(fs.readFileSync(file, 'utf8'), { json: true })
messages = flatten(messages)
oldLocaleMaps[locale] = {}
for (const messageKey of Object.keys(messages as object)) {
const message = (messages as Record<string, unknown>)[messageKey]
if (message && typeof message === 'string' && message !== '') {
oldLocaleMaps[locale][messageKey] = (
messages as Record<string, unknown>
)[messageKey] as object
}
}
}
return oldLocaleMaps
}
type Opts = {
[key: string]: unknown
defaultLocale: string
format?: string
flat?: boolean
overwriteDefault?: boolean
indent?: number
}
const extractMessage = async (
locales: string[],
pattern: string,
buildDir: string,
{
format = 'json',
flat = isJson(format),
defaultLocale = 'en',
overwriteDefault = true,
indent = 2,
...opts
}: Opts = {
defaultLocale: 'en'
}
) => {
if (!Array.isArray(locales)) {
throw new TypeError(`Expected a Array, got ${typeof locales}`)
}
if (typeof pattern !== 'string') {
throw new TypeError(`Expected a string, got ${typeof pattern}`)
}
if (typeof buildDir !== 'string') {
throw new TypeError(`Expected a string, got ${typeof buildDir}`)
}
const ext = isJson(format) ? 'json' : 'yml'
const oldLocaleMaps = loadLocaleFiles(locales, buildDir, ext)
const extractorOptions = {
defaultLocale,
withDescriptions: false,
cwd: process.cwd(),
extractFromFormatMessageCall: true,
...opts
}
const newLocaleMaps = await _extractReactIntl(
locales,
pattern,
extractorOptions
)
return Promise.all(
locales.map((locale) => {
// If the default locale, overwrite the origin file
let localeMap =
locale === defaultLocale && overwriteDefault
? // Create a clone so we can use only current valid messages below
{ ...oldLocaleMaps[locale], ...newLocaleMaps[locale] }
: { ...newLocaleMaps[locale], ...oldLocaleMaps[locale] }
// Only keep existing keys
localeMap = Object.fromEntries(
Object.entries(localeMap).filter(([key]) =>
Object.keys(newLocaleMaps[locale]).includes(key)
)
)
const fomattedLocaleMap: object = flat
? sortKeys(localeMap, { deep: true })
: sortKeys(unflatten(localeMap, { object: true }), { deep: true })
const fn = isJson(format) ? writeJson : writeYaml
return fn(path.resolve(buildDir, locale), fomattedLocaleMap, indent)
})
)
}
extractMessage.extractReactIntl = _extractReactIntl
export default extractMessage
================================================
FILE: src/test/fixtures/custom/a/messages.js
================================================
import { defineMessages } from '../i18n'
export default defineMessages({
hello: {
id: 'a.custom.hello',
defaultMessage: 'hello'
},
world: {
id: 'a.custom.world',
defaultMessage: 'world'
}
})
================================================
FILE: src/test/fixtures/custom/b/messages.js
================================================
import { defineMessages } from '../i18n'
export default defineMessages({
hello: {
id: 'b.custom.message',
defaultMessage: 'Message'
}
})
================================================
FILE: src/test/fixtures/custom/i18n.js
================================================
export { defineMessages } from 'react-intl'
================================================
FILE: src/test/fixtures/default/a/App.js
================================================
import React from 'react'
import { useIntl } from 'react-intl'
export const SubmitButton = () => {
const intl = useIntl()
const label = intl.formatMessage({
id: 'a.submit',
defaultMessage: 'Submit Button'
})
return <button aria-label={label}>{label}</button>
}
================================================
FILE: src/test/fixtures/default/a/messages.js
================================================
import { defineMessages } from 'react-intl'
export default defineMessages({
hello: {
id: 'a.hello',
defaultMessage: 'hello'
},
world: {
id: 'a.world',
defaultMessage: 'world'
}
})
================================================
FILE: src/test/fixtures/default/b/messages.js
================================================
import { defineMessages } from 'react-intl'
export default defineMessages({
hello: {
id: 'b.hello',
defaultMessage: 'hello'
},
world: {
id: 'b.world',
defaultMessage: 'world'
}
})
================================================
FILE: src/test/fixtures/removed/a/messages.js
================================================
import { defineMessages } from 'react-intl'
export default defineMessages({
hello: {
id: 'a.hello',
defaultMessage: 'hello'
}
})
================================================
FILE: src/test/fixtures/removed/b/messages.js
================================================
import { defineMessages } from 'react-intl'
export default defineMessages({
hello: {
id: 'b.hello',
defaultMessage: 'hello'
},
world: {
id: 'b.world',
defaultMessage: 'world'
}
})
================================================
FILE: src/test/fixtures/unsorted/a/messages.js
================================================
import { defineMessages } from 'react-intl'
export default defineMessages({
hello: {
id: 'a.hello',
defaultMessage: 'hello'
},
helloZ: {
id: 'z.hello',
defaultMessage: 'hello'
},
helloC: {
id: 'c.hello',
defaultMessage: 'hello'
},
world: {
id: 'a.world',
defaultMessage: 'world'
}
})
================================================
FILE: src/test/fixtures/unsorted/b/messages.js
================================================
import { defineMessages } from 'react-intl'
export default defineMessages({
hello: {
id: 'b.hello',
defaultMessage: 'hello'
},
helloY: {
id: 'y.hello',
defaultMessage: 'hello'
},
world: {
id: 'b.world',
defaultMessage: 'world'
}
})
================================================
FILE: src/test/json/__snapshots__/test.ts.snap
================================================
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`export json - nest 1`] = `
{
"a": {
"hello": "hello",
"submit": "Submit Button",
"world": "world",
},
"b": {
"hello": "hello",
"world": "world",
},
}
`;
exports[`export json - nest 2`] = `
{
"a": {
"hello": "",
"submit": "",
"world": "",
},
"b": {
"hello": "",
"world": "",
},
}
`;
exports[`export json 1`] = `
{
"a.hello": "hello",
"a.submit": "Submit Button",
"a.world": "world",
"b.hello": "hello",
"b.world": "world",
}
`;
exports[`export json 2`] = `
{
"a.hello": "",
"a.submit": "",
"a.world": "",
"b.hello": "",
"b.world": "",
}
`;
exports[`export json with removed messages 1`] = `
{
"a.hello": "hello",
"a.submit": "Submit Button",
"a.world": "world",
"b.hello": "hello",
"b.world": "world",
}
`;
exports[`export json with removed messages 2`] = `
{
"a.hello": "",
"a.submit": "",
"a.world": "",
"b.hello": "",
"b.world": "",
}
`;
exports[`export json with removed messages 3`] = `
{
"a.hello": "hello",
"b.hello": "hello",
"b.world": "world",
}
`;
exports[`export json with removed messages 4`] = `
{
"a.hello": "",
"b.hello": "",
"b.world": "",
}
`;
exports[`export using custom module 1`] = `
{
"a.custom.hello": "hello",
"a.custom.world": "world",
"b.custom.message": "Message",
}
`;
exports[`export using custom module 2`] = `
{
"a.custom.hello": "",
"a.custom.world": "",
"b.custom.message": "",
}
`;
exports[`sort keys 1`] = `
[
"a.hello",
"a.world",
"b.hello",
"b.world",
"c.hello",
"y.hello",
"z.hello",
]
`;
exports[`sort keys 2`] = `
[
"a.hello",
"a.world",
"b.hello",
"b.world",
"c.hello",
"y.hello",
"z.hello",
]
`;
exports[`with overwriteDefault 1`] = `
{
"a.custom.hello": "hello",
"a.custom.world": "world",
"b.custom.message": "Default Message",
}
`;
exports[`with overwriteDefault 2`] = `
{
"a.custom.hello": "",
"a.custom.world": "",
"b.custom.message": "",
}
`;
================================================
FILE: src/test/json/test.ts
================================================
import fs from 'fs'
import path from 'path'
import tempy from 'tempy'
import m from '../..'
test('export json', async () => {
const tmp = tempy.directory()
await m(['en', 'ja'], 'src/test/fixtures/default/**/*.js', tmp)
const en = JSON.parse(fs.readFileSync(path.resolve(tmp, 'en.json'), 'utf8'))
const ja = JSON.parse(fs.readFileSync(path.resolve(tmp, 'ja.json'), 'utf8'))
expect(en).toMatchSnapshot()
expect(ja).toMatchSnapshot()
})
test('export json with removed messages', async () => {
const tmp = tempy.directory()
await m(['en', 'ja'], 'src/test/fixtures/default/**/*.js', tmp)
const enBefore = JSON.parse(
fs.readFileSync(path.resolve(tmp, 'en.json'), 'utf8')
)
const jaBefore = JSON.parse(
fs.readFileSync(path.resolve(tmp, 'ja.json'), 'utf8')
)
expect(enBefore).toMatchSnapshot()
expect(jaBefore).toMatchSnapshot()
await m(['en', 'ja'], 'src/test/fixtures/removed/**/*.js', tmp)
const en = JSON.parse(fs.readFileSync(path.resolve(tmp, 'en.json'), 'utf8'))
const ja = JSON.parse(fs.readFileSync(path.resolve(tmp, 'ja.json'), 'utf8'))
expect(en).toMatchSnapshot()
expect(ja).toMatchSnapshot()
})
test('export json - nest', async () => {
const tmp = tempy.directory()
await m(['en', 'ja'], 'src/test/fixtures/default/**/*.js', tmp, {
defaultLocale: 'en',
flat: false
})
const en = JSON.parse(fs.readFileSync(path.resolve(tmp, 'en.json'), 'utf8'))
const ja = JSON.parse(fs.readFileSync(path.resolve(tmp, 'ja.json'), 'utf8'))
expect(en).toMatchSnapshot()
expect(ja).toMatchSnapshot()
})
test('sort keys', async () => {
const tmp = tempy.directory()
const enPath = path.resolve(tmp, 'en.json')
const jaPath = path.resolve(tmp, 'ja.json')
await m(['en', 'ja'], 'src/test/fixtures/unsorted/**/*.js', tmp)
const en = JSON.parse(fs.readFileSync(enPath, 'utf8'))
const ja = JSON.parse(fs.readFileSync(jaPath, 'utf8'))
expect(Object.keys(en)).toMatchSnapshot()
expect(Object.keys(ja)).toMatchSnapshot()
})
test('export using custom module', async () => {
const tmp = tempy.directory()
await m(['en', 'ja'], 'src/test/fixtures/custom/**/*.js', tmp, {
defaultLocale: 'en',
moduleSourceName: '../i18n'
})
const en = JSON.parse(fs.readFileSync(path.resolve(tmp, 'en.json'), 'utf8'))
const ja = JSON.parse(fs.readFileSync(path.resolve(tmp, 'ja.json'), 'utf8'))
expect(en).toMatchSnapshot()
expect(ja).toMatchSnapshot()
})
test('with overwriteDefault', async () => {
const tmp = tempy.directory()
fs.writeFileSync(
path.resolve(tmp, 'en.json'),
JSON.stringify(
{
'a.custom.hello': 'hello',
'a.custom.world': 'world',
'b.custom.message': 'Default Message'
},
null,
2
),
'utf8'
)
await m(['en', 'ja'], 'src/test/fixtures/custom/**/*.js', tmp, {
defaultLocale: 'en',
moduleSourceName: '../i18n',
overwriteDefault: false
})
const en = JSON.parse(fs.readFileSync(path.resolve(tmp, 'en.json'), 'utf8'))
const ja = JSON.parse(fs.readFileSync(path.resolve(tmp, 'ja.json'), 'utf8'))
expect(en).toMatchSnapshot()
expect(ja).toMatchSnapshot()
})
================================================
FILE: src/test/test.ts
================================================
import m from '..'
test('errors', async () => {
// @ts-expect-error testing invalid argument type
await expect(m('hello')).rejects.toThrow('Expected a Array')
// @ts-expect-error testing invalid argument type
await expect(m(['en', 'ja'], 2)).rejects.toThrow('Expected a string')
// @ts-expect-error testing invalid argument type
await expect(m(['en', 'ja'], 'app/', 2)).rejects.toThrow('Expected a string')
})
================================================
FILE: src/test/yaml/__snapshots__/test.ts.snap
================================================
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`export yaml - flat 1`] = `
{
"a.hello": "hello",
"a.submit": "Submit Button",
"a.world": "world",
"b.hello": "hello",
"b.world": "world",
}
`;
exports[`export yaml - flat 2`] = `
{
"a.hello": "",
"a.submit": "",
"a.world": "",
"b.hello": "",
"b.world": "",
}
`;
exports[`export yaml 1`] = `
{
"a": {
"hello": "hello",
"submit": "Submit Button",
"world": "world",
},
"b": {
"hello": "hello",
"world": "world",
},
}
`;
exports[`export yaml 2`] = `
{
"a": {
"hello": "",
"submit": "",
"world": "",
},
"b": {
"hello": "",
"world": "",
},
}
`;
exports[`exsit yaml 1`] = `
{
"a": {
"hello": "hello",
"submit": "Submit Button",
"world": "world",
},
"b": {
"hello": "hello",
"world": "world",
},
}
`;
exports[`exsit yaml 2`] = `
{
"a": {
"hello": "hello2",
"submit": "",
"world": "",
},
"b": {
"hello": "",
"world": "",
},
}
`;
================================================
FILE: src/test/yaml/test.ts
================================================
import fs from 'fs'
import path from 'path'
import tempy from 'tempy'
import tempWrite from 'temp-write'
import yaml from 'js-yaml'
import m from '../..'
const fixturesPath = 'src/test/fixtures/default/**/*.js'
const yamlLoad = (tmp: string, file = '') =>
yaml.load(fs.readFileSync(path.resolve(tmp, file), 'utf8'))
const defaultLocale = 'en'
test('export yaml', async () => {
const tmp = tempy.directory()
await m(['en', 'ja'], fixturesPath, tmp, { defaultLocale, format: 'yaml' })
expect(yamlLoad(tmp, 'en.yml')).toMatchSnapshot()
expect(yamlLoad(tmp, 'ja.yml')).toMatchSnapshot()
})
test('export yaml - flat', async () => {
const tmp = tempy.directory()
await m(['en', 'ja'], fixturesPath, tmp, {
defaultLocale,
format: 'yaml',
flat: true
})
expect(yamlLoad(tmp, 'en.yml')).toMatchSnapshot()
expect(yamlLoad(tmp, 'ja.yml')).toMatchSnapshot()
})
test('exsit yaml', async () => {
const x = { a: { hello: 'hello2' } }
const tmpEn = tempWrite.sync(yaml.dump(x), 'en.yml')
await m(['en'], fixturesPath, path.dirname(tmpEn), {
defaultLocale,
format: 'yaml'
})
expect(yaml.load(fs.readFileSync(tmpEn, 'utf8'))).toMatchSnapshot()
const tmpJa = tempWrite.sync(yaml.dump(x), 'ja.yml')
await m(['ja'], fixturesPath, path.dirname(tmpJa), {
defaultLocale,
format: 'yaml'
})
expect(yaml.load(fs.readFileSync(tmpJa, 'utf8'))).toMatchSnapshot()
})
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
/* Language and Environment */
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"module": "Node16",
"moduleResolution": "Node16",
/* Modules */
"allowImportingTsExtensions": false,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
/* Emit */
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"removeComments": false,
"importHelpers": false,
"downlevelIteration": false,
/* Interop Constraints */
"allowJs": false,
"checkJs": false,
/* Type Checking */
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": false,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": false,
/* Completeness */
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts",
"**/test.*"
]
}
gitextract_9ar36m_8/ ├── .all-contributorsrc ├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ └── test.yml ├── .gitignore ├── .husky/ │ └── pre-commit ├── .prettierignore ├── .prettierrc ├── CODE_OF_CONDUCT.md ├── eslint.config.mjs ├── example/ │ ├── basic/ │ │ ├── babel.config.js │ │ ├── i18n/ │ │ │ ├── .keep │ │ │ ├── en.json │ │ │ └── ja.json │ │ ├── package.json │ │ └── src/ │ │ ├── App.jsx │ │ └── messages.js │ └── with-typescript/ │ ├── babel.config.js │ ├── i18n/ │ │ ├── en.json │ │ └── ja.json │ ├── package.json │ └── src/ │ ├── App.tsx │ └── messages.ts ├── jest.config.js ├── license ├── package.json ├── readme.md ├── src/ │ ├── cli.ts │ ├── extract-react-intl/ │ │ ├── index.ts │ │ ├── readme.md │ │ └── test/ │ │ ├── __snapshots__/ │ │ │ └── test.ts.snap │ │ ├── fixtures/ │ │ │ ├── .babelrc │ │ │ ├── components/ │ │ │ │ ├── App/ │ │ │ │ │ ├── index.js │ │ │ │ │ └── messages.js │ │ │ │ ├── Greeting/ │ │ │ │ │ ├── index.js │ │ │ │ │ └── messages.js │ │ │ │ └── LanguageProvider/ │ │ │ │ └── index.js │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── pluginOrdering/ │ │ │ ├── .babelrc │ │ │ └── messages.js │ │ ├── resolution/ │ │ │ ├── .babelrc │ │ │ └── messages.js │ │ └── test.ts │ ├── global.d.ts │ ├── index.ts │ └── test/ │ ├── fixtures/ │ │ ├── custom/ │ │ │ ├── a/ │ │ │ │ └── messages.js │ │ │ ├── b/ │ │ │ │ └── messages.js │ │ │ └── i18n.js │ │ ├── default/ │ │ │ ├── a/ │ │ │ │ ├── App.js │ │ │ │ └── messages.js │ │ │ └── b/ │ │ │ └── messages.js │ │ ├── removed/ │ │ │ ├── a/ │ │ │ │ └── messages.js │ │ │ └── b/ │ │ │ └── messages.js │ │ └── unsorted/ │ │ ├── a/ │ │ │ └── messages.js │ │ └── b/ │ │ └── messages.js │ ├── json/ │ │ ├── __snapshots__/ │ │ │ └── test.ts.snap │ │ └── test.ts │ ├── test.ts │ └── yaml/ │ ├── __snapshots__/ │ │ └── test.ts.snap │ └── test.ts └── tsconfig.json
SYMBOL INDEX (12 symbols across 4 files)
FILE: src/extract-react-intl/index.ts
type LocaleMap (line 16) | type LocaleMap = Record<string, Record<string, unknown>>
type Options (line 60) | type Options = {
type Message (line 69) | type Message = {
type CacheData (line 75) | type CacheData = {
type File (line 80) | type File = FileDescriptor & {
FILE: src/extract-react-intl/test/fixtures/components/App/index.js
class App (line 14) | class App extends Component {
method render (line 15) | render() {
FILE: src/extract-react-intl/test/fixtures/components/Greeting/index.js
class Greeting (line 18) | class Greeting extends Component {
method render (line 21) | render() {
function defaultMessage (line 46) | function defaultMessage() {
FILE: src/index.ts
function loadLocaleFiles (line 25) | function loadLocaleFiles(locales: string[], buildDir: string, ext: strin...
type Opts (line 66) | type Opts = {
Condensed preview — 66 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (72K chars).
[
{
"path": ".all-contributorsrc",
"chars": 4424,
"preview": "{\n \"projectName\": \"extract-react-intl-messages\",\n \"projectOwner\": \"akameco\",\n \"files\": [\"readme.md\"],\n \"imageSize\": "
},
{
"path": ".babelrc",
"chars": 164,
"preview": "{\n \"presets\": [\n [\n \"@babel/preset-env\",\n {\n \"targets\": {\n \"node\": \"current\"\n }\n "
},
{
"path": ".editorconfig",
"chars": 147,
"preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ni"
},
{
"path": ".eslintrc",
"chars": 114,
"preview": "{\n \"extends\": [\"precure/auto\"],\n \"rules\": {\n \"@typescript-eslint/explicit-function-return-type\": \"off\"\n }\n}\n"
},
{
"path": ".gitattributes",
"chars": 29,
"preview": "* text=auto\n*.js text eol=lf\n"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 420,
"preview": "<!--\nThanks for your interest in the project.\nI appreciate bugs filed and PRs submitted!\nI'll probably ask you to submit"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 738,
"preview": "<!--\nThanks for your interest in the project. I appreciate bugs filed and PRs submitted!\nEnglish/日本語(日本語で入力して大丈夫です。日本語の方"
},
{
"path": ".github/workflows/test.yml",
"chars": 413,
"preview": "name: test\n\non: [push]\n\njobs:\n build:\n runs-on: ubuntu-latest\n strategy:\n matrix:\n # LTS (20.x) and l"
},
{
"path": ".gitignore",
"chars": 359,
"preview": "# Dependencies\nnode_modules/\n\n# Build outputs\nlib/\ndist/\ncompiled/\n\n# Cache files\n.eslintcache\n.test-cache\n\n# OS files\n."
},
{
"path": ".husky/pre-commit",
"chars": 16,
"preview": "npx lint-staged\n"
},
{
"path": ".prettierignore",
"chars": 46,
"preview": "**/test/fixtures/**\n.github\ndist\npackage.json\n"
},
{
"path": ".prettierrc",
"chars": 70,
"preview": "{\n \"semi\": false,\n \"singleQuote\": true,\n \"trailingComma\": \"none\"\n}\n"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3216,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "eslint.config.mjs",
"chars": 416,
"preview": "// ESLint v9+ config\nimport js from '@eslint/js'\nimport tseslint from 'typescript-eslint'\n\nexport default [\n js.configs"
},
{
"path": "example/basic/babel.config.js",
"chars": 97,
"preview": "module.exports = function (api) {\n api.cache(true)\n\n return {\n presets: ['react-app']\n }\n}\n"
},
{
"path": "example/basic/i18n/.keep",
"chars": 0,
"preview": ""
},
{
"path": "example/basic/i18n/en.json",
"chars": 135,
"preview": "{\n \"App\": {\n \"hello\": \"Hello Button\",\n \"submit\": \"Submit Button\"\n },\n \"a\": {\n \"hello\": \"hello\",\n \"world\":"
},
{
"path": "example/basic/i18n/ja.json",
"chars": 100,
"preview": "{\n \"App\": {\n \"hello\": \"\",\n \"submit\": \"\"\n },\n \"a\": {\n \"hello\": \"\",\n \"world\": \"\"\n }\n}\n"
},
{
"path": "example/basic/package.json",
"chars": 397,
"preview": "{\n \"name\": \"basic\",\n \"version\": \"1.0.0\",\n \"license\": \"MIT\",\n \"scripts\": {\n \"i18n\": \"NODE_ENV=development extract-"
},
{
"path": "example/basic/src/App.jsx",
"chars": 501,
"preview": "import React from 'react'\nimport { injectIntl, useIntl } from 'react-intl'\n\nexport const SubmitButton = injectIntl(({ in"
},
{
"path": "example/basic/src/messages.js",
"chars": 260,
"preview": "/* eslint-disable import/no-extraneous-dependencies */\nimport { defineMessages } from 'react-intl'\n\nexport default defin"
},
{
"path": "example/with-typescript/babel.config.js",
"chars": 135,
"preview": "module.exports = function (api) {\n api.cache(true)\n\n return {\n presets: ['@babel/preset-react', '@babel/preset-type"
},
{
"path": "example/with-typescript/i18n/en.json",
"chars": 135,
"preview": "{\n \"App\": {\n \"hello\": \"Hello Button\",\n \"submit\": \"Submit Button\"\n },\n \"a\": {\n \"hello\": \"hello\",\n \"world\":"
},
{
"path": "example/with-typescript/i18n/ja.json",
"chars": 100,
"preview": "{\n \"App\": {\n \"hello\": \"\",\n \"submit\": \"\"\n },\n \"a\": {\n \"hello\": \"\",\n \"world\": \"\"\n }\n}\n"
},
{
"path": "example/with-typescript/package.json",
"chars": 453,
"preview": "{\n \"name\": \"with-typescript\",\n \"version\": \"1.0.0\",\n \"license\": \"MIT\",\n \"scripts\": {\n \"i18n\": \"extract-messages -l"
},
{
"path": "example/with-typescript/src/App.tsx",
"chars": 501,
"preview": "import React from 'react'\nimport { injectIntl, useIntl } from 'react-intl'\n\nexport const SubmitButton = injectIntl(({ in"
},
{
"path": "example/with-typescript/src/messages.ts",
"chars": 205,
"preview": "import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n hello: {\n id: 'a.hello',\n defaultMe"
},
{
"path": "jest.config.js",
"chars": 470,
"preview": "export default {\n testPathIgnorePatterns: [\n '<rootDir>[/\\\\\\\\](dist|compiled|node_modules)[/\\\\\\\\]'\n ],\n testEnviro"
},
{
"path": "license",
"chars": 1111,
"preview": "The MIT License (MIT)\n\nCopyright (c) akameco <akameco.t@gmail.com> (akameco.github.io)\n\nPermission is hereby granted, fr"
},
{
"path": "package.json",
"chars": 2626,
"preview": "{\n \"name\": \"extract-react-intl-messages\",\n \"version\": \"5.0.0\",\n \"description\": \"Extract react-intl messages\",\n \"lice"
},
{
"path": "readme.md",
"chars": 12403,
"preview": "# extract-react-intl-messages\n\n["
},
{
"path": "src/cli.ts",
"chars": 2185,
"preview": "#!/usr/bin/env node\n \nimport meow from 'meow'\nimport extractMessage from './index.js'\n\nconst cli = meow(\n `\n Usage\n $"
},
{
"path": "src/extract-react-intl/index.ts",
"chars": 5280,
"preview": "import path from 'path'\nimport { glob } from 'glob'\nimport { promisify } from 'util'\nimport merge from 'lodash.merge'\nim"
},
{
"path": "src/extract-react-intl/readme.md",
"chars": 465,
"preview": "# extract-react-intl\n\n## API\n\n### extractReactIntl(locales, pattern, [options])\n\nReturn a `Promise` wrapped extracted me"
},
{
"path": "src/extract-react-intl/test/__snapshots__/test.ts.snap",
"chars": 3078,
"preview": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`babel plugin execution order 1`] = `\n{\n \"en\": {\n"
},
{
"path": "src/extract-react-intl/test/fixtures/.babelrc",
"chars": 530,
"preview": "{\n \"presets\": [\n [\n \"@babel/preset-env\",\n {\n \"targets\": {\n \"browsers\": [\n \"last"
},
{
"path": "src/extract-react-intl/test/fixtures/components/App/index.js",
"chars": 649,
"preview": "// @flow\nimport React, { Component } from 'react'\nimport { FormattedMessage, injectIntl } from 'react-intl'\nimport Greet"
},
{
"path": "src/extract-react-intl/test/fixtures/components/App/messages.js",
"chars": 186,
"preview": "// @flow\nimport { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n // hello message description\n h"
},
{
"path": "src/extract-react-intl/test/fixtures/components/Greeting/index.js",
"chars": 927,
"preview": "// @flow\nimport React, { Component } from 'react'\nimport {\n FormattedMessage,\n FormattedNumber,\n FormattedRelative\n} "
},
{
"path": "src/extract-react-intl/test/fixtures/components/Greeting/messages.js",
"chars": 428,
"preview": "// @flow\nimport { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n // Welcome message description\n "
},
{
"path": "src/extract-react-intl/test/fixtures/components/LanguageProvider/index.js",
"chars": 948,
"preview": "// @flow\nimport React, { Component } from 'react'\nimport { addLocaleData, IntlProvider } from 'react-intl'\nimport enLoca"
},
{
"path": "src/extract-react-intl/test/fixtures/index.html",
"chars": 197,
"preview": "<!doctype html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <title>Example</title>\n </head>\n <body>\n <div id=\""
},
{
"path": "src/extract-react-intl/test/fixtures/index.js",
"chars": 274,
"preview": "// @flow\nimport React from 'react'\nimport ReactDOM from 'react-dom'\nimport App from './components/App'\nimport LanguagePr"
},
{
"path": "src/extract-react-intl/test/pluginOrdering/.babelrc",
"chars": 74,
"preview": "{\n \"presets\": [\"@babel/preset-flow\"],\n \"plugins\": [\"react-intl-auto\"]\n}\n"
},
{
"path": "src/extract-react-intl/test/pluginOrdering/messages.js",
"chars": 95,
"preview": "import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n test: 'auto'\n})\n"
},
{
"path": "src/extract-react-intl/test/resolution/.babelrc",
"chars": 69,
"preview": "{\n \"presets\": [\"@babel/preset-flow\"],\n \"plugins\": [\"react-intl\"]\n}\n"
},
{
"path": "src/extract-react-intl/test/resolution/messages.js",
"chars": 137,
"preview": "import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n test: {\n id: 'test',\n defaultMessag"
},
{
"path": "src/extract-react-intl/test/test.ts",
"chars": 1909,
"preview": "import m from '..'\n\nconst pattern = 'src/extract-react-intl/test/fixtures/**/*.js'\nconst locales = ['en', 'ja']\n\ntest('e"
},
{
"path": "src/global.d.ts",
"chars": 150,
"preview": "declare module 'read-babelrc-up' {\n function sync(opts: { cwd: string }): {\n path: string\n babel: import('@babel/"
},
{
"path": "src/index.ts",
"chars": 4040,
"preview": "import path from 'path'\nimport fs from 'fs'\nimport yaml from 'js-yaml'\nimport { promisify } from 'util'\nimport { flatten"
},
{
"path": "src/test/fixtures/custom/a/messages.js",
"chars": 216,
"preview": "import { defineMessages } from '../i18n'\n\nexport default defineMessages({\n hello: {\n id: 'a.custom.hello',\n defau"
},
{
"path": "src/test/fixtures/custom/b/messages.js",
"chars": 150,
"preview": "import { defineMessages } from '../i18n'\n\nexport default defineMessages({\n hello: {\n id: 'b.custom.message',\n def"
},
{
"path": "src/test/fixtures/custom/i18n.js",
"chars": 44,
"preview": "export { defineMessages } from 'react-intl'\n"
},
{
"path": "src/test/fixtures/default/a/App.js",
"chars": 278,
"preview": "import React from 'react'\nimport { useIntl } from 'react-intl'\n\nexport const SubmitButton = () => {\n const intl = useIn"
},
{
"path": "src/test/fixtures/default/a/messages.js",
"chars": 205,
"preview": "import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n hello: {\n id: 'a.hello',\n defaultMe"
},
{
"path": "src/test/fixtures/default/b/messages.js",
"chars": 205,
"preview": "import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n hello: {\n id: 'b.hello',\n defaultMe"
},
{
"path": "src/test/fixtures/removed/a/messages.js",
"chars": 142,
"preview": "import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n hello: {\n id: 'a.hello',\n defaultMe"
},
{
"path": "src/test/fixtures/removed/b/messages.js",
"chars": 205,
"preview": "import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n hello: {\n id: 'b.hello',\n defaultMe"
},
{
"path": "src/test/fixtures/unsorted/a/messages.js",
"chars": 333,
"preview": "import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n hello: {\n id: 'a.hello',\n defaultMe"
},
{
"path": "src/test/fixtures/unsorted/b/messages.js",
"chars": 269,
"preview": "import { defineMessages } from 'react-intl'\n\nexport default defineMessages({\n hello: {\n id: 'b.hello',\n defaultMe"
},
{
"path": "src/test/json/__snapshots__/test.ts.snap",
"chars": 2035,
"preview": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`export json - nest 1`] = `\n{\n \"a\": {\n \"hello\""
},
{
"path": "src/test/json/test.ts",
"chars": 3135,
"preview": "import fs from 'fs'\nimport path from 'path'\nimport tempy from 'tempy'\nimport m from '../..'\n\ntest('export json', async ("
},
{
"path": "src/test/test.ts",
"chars": 423,
"preview": "import m from '..'\n\ntest('errors', async () => {\n // @ts-expect-error testing invalid argument type\n await expect(m('h"
},
{
"path": "src/test/yaml/__snapshots__/test.ts.snap",
"chars": 1028,
"preview": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`export yaml - flat 1`] = `\n{\n \"a.hello\": \"hello\""
},
{
"path": "src/test/yaml/test.ts",
"chars": 1412,
"preview": "import fs from 'fs'\nimport path from 'path'\nimport tempy from 'tempy'\nimport tempWrite from 'temp-write'\nimport yaml fro"
},
{
"path": "tsconfig.json",
"chars": 1474,
"preview": "{\n \"compilerOptions\": {\n /* Language and Environment */\n \"target\": \"ES2022\",\n \"lib\": [\"ES2022\", \"DOM\"],\n \"m"
}
]
About this extraction
This page contains the full source code of the akameco/extract-react-intl-messages GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 66 files (61.9 KB), approximately 19.8k tokens, and a symbol index with 12 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.