diff --git a/.eslintignore b/.eslintignore index 3e2702ce5..a254f0f8c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,6 +2,6 @@ **/node_modules/* **/fixtures/* **/locale/* -docs/* +website/* README.md **/npm/* \ No newline at end of file diff --git a/.github/workflows/deploy-main-docs.yml b/.github/workflows/deploy-main-docs.yml deleted file mode 100644 index 794070a5e..000000000 --- a/.github/workflows/deploy-main-docs.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: deploy-main-docs - -on: - push: - branches: - - main - paths: - - docs/** - -jobs: - deploy-docs: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Setup node - uses: actions/setup-node@v3 - with: - node-version: 16 - - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: '3.9' - cache: 'pip' - - - name: Deploy documentation - run: | - cd docs/ - ./vercel-build.sh - npx vercel --public --yes --prod --token ${{secrets.NOW_TOKEN}} --name lingui-docs _build/html diff --git a/.github/workflows/docs-suite.yml b/.github/workflows/docs-suite.yml new file mode 100644 index 000000000..f2d53d6da --- /dev/null +++ b/.github/workflows/docs-suite.yml @@ -0,0 +1,36 @@ +name: docs-suite + +on: + pull_request: + branches: + - '*' + paths: + - website/** + +jobs: + validate: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Install dependencies + working-directory: website + run: yarn install --frozen-lockfile --pure-lockfile + + - name: Build + working-directory: website + run: yarn build + + - name: Lint + working-directory: website + run: yarn lint + + - name: Check Formatting + working-directory: website + run: yarn checkFormat diff --git a/.github/workflows/release-docs.yml b/.github/workflows/release-docs.yml new file mode 100644 index 000000000..d6e465756 --- /dev/null +++ b/.github/workflows/release-docs.yml @@ -0,0 +1,38 @@ +name: release-docs + +on: + workflow_dispatch: + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Install dependencies + working-directory: website + run: yarn install --frozen-lockfile --pure-lockfile + + - name: Build + working-directory: website + run: yarn build + + - name: Deploy documentation + working-directory: website + run: | + npx vercel --public --yes --prod --token ${{ secrets.NOW_TOKEN }} --name lingui-docs build + + - name: Update Algolia index + uses: darrenjennings/algolia-docsearch-action@v0.2.0 + with: + algolia_application_id: ${{ vars.ALGOLIA_APP_ID }} + algolia_api_key: ${{ secrets.ALGOLIA_WRITE_API_KEY }} + file: 'website/tools/algolia/config.json' diff --git a/.gitignore b/.gitignore index f3d1a0073..f07902b7e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ build/packages/ .yalc/ yalc.lock packages/**/build -docs/_build/ npm-debug.log yarn-error.log diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..38be88cf3 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders 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, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +https://github.com/lingui/js-lingui/discussions. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bfe937a7e..0ccce10f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,27 +1,48 @@ +# Contributing + +:tada: First off, thanks for taking the time to contribute! :tada: + +The following is a set of guidelines for contributing to Lingui. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. + +This project and everyone participating in it are governed by the [Code of Conduct](/CODE_OF_CONDUCT.md). We expect that all community members adhere to the guidelines within. + **Working on your first Pull Request?** You can learn how from this _free_ series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github) ## Contributing to the docs -Documentation uses Sphinx and reStructuredText. Source inside the -[docs](https://github.com/lingui/js-lingui/tree/main/docs) directory. +The documentation is based on [Docusaurus](https://docusaurus.io/) framework. Source inside the [website](https://github.com/lingui/js-lingui/tree/main/website) directory. + +- Go to the `website` directory: + + ```sh + cd website + ``` + +- Install dependencies: + + ```sh + yarn install + ``` -1. Go to the `docs` directory +- To build the docs, watch for changes and preview documentation locally at [http://localhost:3000/](http://localhost:3000/): + + ```sh + yarn start + ``` -2. Run `pipenv install` to setup Python environemnt (requires Python 3.6). - If you encounter `ValueError('unknown locale: %s' % localename)`, - run `export LC_ALL=en_US.UTF-8 && export LANG=en_US.UTF-8` and try again. +- It's also possible to run `yarn build` for single build. Incremental builds are much faster than the first one as only changed files are built. -3. Run `pipenv run make livehtml` to build the docs, watch for changes and preview - documentation locally at [http://127.0.0.1:8000](http://127.0.0.1:8000). +- Please lint and validate the documentation before submitting any changes: -4. It's also possible to run `pipenv run make html` for single build. Incremental builds - are much faster than the first one as only changed files are built. + ```sh + yarn lint + yarn checkFormat + ``` ## Contributing the code -This project uses [yarn][yarninstall] package manager. Please follow -[official][yarninstall] docs to install it. +This project uses [yarn][yarninstall] package manager. Please follow [official][yarninstall] docs to install it. ### Setup local environment @@ -32,10 +53,7 @@ This project uses [yarn][yarninstall] package manager. Please follow cd js-lingui ``` -2. Install development packages. This project uses - [yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/) instead of Lerna, - so running `yarn` installs all development packages and also dependencies for all - workspaces (inside `packages/*`). +2. Install development packages. This project uses [yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/) instead of Lerna, so running `yarn` installs all development packages and also dependencies for all workspaces (inside `packages/*`). ```sh yarn @@ -51,22 +69,20 @@ This project uses [yarn][yarninstall] package manager. Please follow yarn test ``` - NOTE: if you are using an IDE to run test make sure to use the right Jest config. - For unit tests use `-c scripts/jest/config.unit.js`. Integration tests use - build packages (created using `yarn release:build`) and config `-c scripts/jest/config.integration.js`. - See [package.json](./package.json) for more info. - - If you run tests manually instead of using `yarn watch` or `yarn test` commands and your tests - fail due to missing locale data (typically you'll get wrong number and currency formating) - make sure you have `NODE_ICU_DATA` variable set: `NODE_ICU_DATA=node_modules/full-icu`. + > **Note** + > If you are using an IDE to run test make sure to use the right Jest config. + > For unit tests use `-c scripts/jest/config.unit.js`. Integration tests use + > build packages (created using `yarn release:build`) and config `-c scripts/jest/config.integration.js`. + > See [package.json](./package.json) for more info. + > If you run tests manually instead of using `yarn watch` or `yarn test` commands and your tests + > fail due to missing locale data (typically you'll get wrong number and currency formating) + > make sure you have `NODE_ICU_DATA` variable set: `NODE_ICU_DATA=node_modules/full-icu`. ### Using development version in your project -After you successfully fix a bug or add a new feature, you most probably want -to test it in your project as soon as possible. +After you successfully fix a bug or add a new feature, you most probably want to test it in your project as soon as possible. -`jsLingui` uses [verdaccio](https://verdaccio.org/), a lightweight local NPM registry, to install -local build of packages in examples. You can do the same in your project: +`jsLingui` uses [verdaccio](https://verdaccio.org/), a lightweight local NPM registry, to install local build of packages in examples. You can do the same in your project: 1. Run `verdaccio` locally in docker (follow [verdaccio guide](https://verdaccio.org/docs/en/what-is-verdaccio.html) if you don't want to run it in Docker): @@ -106,18 +122,16 @@ local build of packages in examples. You can do the same in your project: ### Finalize changes -Please make sure that all tests pass and linter doesn't report any error before -submitting a PR (Don't worry though! If you can't figure out the problem, create a PR -anyway and we'll help you). +Please make sure that all tests pass and linter doesn't report any error before submitting a PR (Don't worry though! If you can't figure out the problem, create a PR anyway, and we'll help you). - `yarn lint:all` - Linting & Type testing - `yarn test` - Quick test suite (sufficient) - `yarn release:test` - Full test suite (recommended) -`yarn release:test` builds all packages, simulates creating packages for NPM, runs unit -tests and finally runs integration tests using production build. +`yarn release:test` builds all packages, simulates creating packages for NPM, runs unit tests and finally runs integration tests using production build. -**Note**: Don't commit `scripts/build/results.json` created by `yarn release:test`. +> **Note** +> Don't commit `scripts/build/results.json` created by `yarn release:test`. Now you can create PR and let CI service do their work! diff --git a/LICENSE b/LICENSE index 868be5858..c6f6622e2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2017 Tomáš Ehrlich +Copyright (c) 2017-2022 Tomáš Ehrlich, (c) 2022-2023 Crowdin. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 666aa5f04..3581d2e54 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ [![Sponsors on Open Collective][Badge-ocsponsors]][ocsponsors-local] [![Join the community on Discord][Badge-Discord]][Discord] -[**Documentation**][Documentation] · [**Documentation 2.x**](https://js-lingui-git-stable-2x.lingui-js.vercel.app/) · [**Quickstart**](#quickstart) · [**Example**](#example) · [**Support**](#support) · [**Contribute**](#contribute) · [**Licence**](#licence) +[**Documentation**][Documentation] · [**Quickstart**](#quickstart) · [**Example**](#example) · [**Support**](#support) · [**Contribute**](#contribute) · [**License**](#license) > Internationalization is the design and development of a product, application or document content that enables easy localization for target audiences that vary in culture, region, or language. @@ -111,10 +111,9 @@ documentation to implement features and fixing bugs. - Do you use **Lingui** in production site? Let us know! - Have you seen any interesting talk or article about **i18n**? - [Share it](https://github.com/lingui/js-lingui/edit/main/docs/misc/talks-about-i18n.rst)! + [Share it](https://github.com/lingui/js-lingui/edit/main/website/docs/misc/resources.md)! - Have you found a bug or do you want to suggest a new feature? [Create an issue][Issues]! -- Do you want to improve the docs and write some code? - Read the [contributors guide][Contributing] and send a PR! +- Do you want to improve the docs and write some code? Read the [contributors guide][Contributing] and send a PR! ### Contributors @@ -147,7 +146,6 @@ Support this project by becoming a sponsor. Your logo will show up here with a l The project is licensed under the [MIT][License] license. -[ReactIntl]: https://github.com/yahoo/react-intl [Documentation]: https://lingui.js.org/ [TutorialReact]: https://lingui.js.org/tutorials/react.html [TutorialReactNative]: https://lingui.js.org/tutorials/react-native.html @@ -159,8 +157,6 @@ The project is licensed under the [MIT][License] license. [Badge-MainSuite-GithubCI]: https://github.com/lingui/js-lingui/workflows/main-suite/badge.svg [Badge-ReleaseWorkflowTesting-GithubCI]: https://github.com/lingui/js-lingui/workflows/release-workflow-test/badge.svg -[Badge-CI]: https://img.shields.io/circleci/project/github/lingui/js-lingui/main.svg -[Badge-AppVeyor]: https://ci.appveyor.com/api/projects/status/0wjdm3qofrjo2c4n/branch/main?svg=true [Badge-Coverage]: https://img.shields.io/codecov/c/github/lingui/js-lingui/main.svg [Badge-PRWelcome]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square [Badge-ocbackers]: https://opencollective.com/js-lingui/backers/badge.svg @@ -171,14 +167,11 @@ The project is licensed under the [MIT][License] license. [Img-Backers]: https://opencollective.com/js-lingui/backers.svg?width=890 [Backers]: https://opencollective.com/js-lingui#backers -[CI]: https://circleci.com/gh/lingui/js-lingui/tree/main -[AppVeyor]: https://ci.appveyor.com/project/tricoder42/js-lingui/branch/main [Coverage]: https://codecov.io/gh/lingui/js-lingui [License]: https://github.com/lingui/js-lingui/blob/main/LICENSE [Contributing]: https://github.com/lingui/js-lingui/blob/main/CONTRIBUTING.md [Issues]: https://github.com/lingui/js-lingui/issues/new/choose [PRWelcome]: http://makeapullrequest.com -[Indiegogo]: https://igg.me/at/js-lingui/x/4367619 [ocbackers-local]: #backers [ocsponsors-local]: #sponsors [BundleReact]: https://bundlephobia.com/result?p=@lingui/react diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index 873bf2a35..000000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -lingui.js.org diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 1024afc61..000000000 --- a/docs/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = python -m sphinx -SPHINXPROJ = LinguiJS -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -livehtml: - sphinx-autobuild -b html $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html" - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/Pipfile b/docs/Pipfile deleted file mode 100644 index e64acda48..000000000 --- a/docs/Pipfile +++ /dev/null @@ -1,16 +0,0 @@ -[[source]] -name = "pypi" -url = "https://pypi.python.org/simple" -verify_ssl = true - -[dev-packages] - -[packages] -sphinx = "*" -sphinx-rtd-theme = "*" -jsx-lexer = "*" -sphinx-autobuild = "*" -sphinx-sitemap = "*" - -[requires] -python_version = "3.9" diff --git a/docs/Pipfile.lock b/docs/Pipfile.lock deleted file mode 100644 index c824fc951..000000000 --- a/docs/Pipfile.lock +++ /dev/null @@ -1,320 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "c60bb627831a8373ea6941482550011bf4d98ea4878f47af7ffae1615e318cab" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.9" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "alabaster": { - "hashes": [ - "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", - "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" - ], - "version": "==0.7.12" - }, - "babel": { - "hashes": [ - "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe", - "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6" - ], - "markers": "python_version >= '3.6'", - "version": "==2.11.0" - }, - "certifi": { - "hashes": [ - "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", - "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" - ], - "markers": "python_version >= '3.6'", - "version": "==2022.12.7" - }, - "charset-normalizer": { - "hashes": [ - "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", - "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" - ], - "markers": "python_version >= '3.6'", - "version": "==2.1.1" - }, - "colorama": { - "hashes": [ - "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", - "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", - "version": "==0.4.6" - }, - "docutils": { - "hashes": [ - "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125", - "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.17.1" - }, - "idna": { - "hashes": [ - "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", - "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" - ], - "markers": "python_version >= '3.5'", - "version": "==3.4" - }, - "imagesize": { - "hashes": [ - "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", - "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.4.1" - }, - "importlib-metadata": { - "hashes": [ - "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad", - "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d" - ], - "markers": "python_version < '3.10'", - "version": "==6.0.0" - }, - "jinja2": { - "hashes": [ - "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", - "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" - ], - "markers": "python_version >= '3.7'", - "version": "==3.1.2" - }, - "jsx-lexer": { - "hashes": [ - "sha256:bff51c2a2faa2c682cbc9a0f360b8c65e4153eb1df06988e8dad34373d3f9995", - "sha256:ca22483ced80a92e45fa1855da7cf99309852c0637842a79a759e10ea57b904d" - ], - "index": "pypi", - "version": "==2.0.0" - }, - "livereload": { - "hashes": [ - "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869", - "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4" - ], - "version": "==2.6.3" - }, - "markupsafe": { - "hashes": [ - "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", - "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", - "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", - "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", - "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", - "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", - "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", - "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", - "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", - "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", - "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", - "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", - "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", - "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", - "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", - "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", - "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", - "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", - "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", - "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", - "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", - "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", - "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", - "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", - "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", - "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", - "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", - "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", - "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", - "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", - "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", - "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", - "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", - "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", - "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", - "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", - "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", - "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", - "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", - "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.1" - }, - "packaging": { - "hashes": [ - "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3", - "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3" - ], - "markers": "python_version >= '3.7'", - "version": "==22.0" - }, - "pygments": { - "hashes": [ - "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297", - "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717" - ], - "markers": "python_version >= '3.6'", - "version": "==2.14.0" - }, - "pytz": { - "hashes": [ - "sha256:7ccfae7b4b2c067464a6733c6261673fdb8fd1be905460396b97a073e9fa683a", - "sha256:93007def75ae22f7cd991c84e02d434876818661f8df9ad5df9e950ff4e52cfd" - ], - "version": "==2022.7" - }, - "requests": { - "hashes": [ - "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", - "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" - ], - "markers": "python_version >= '3.7' and python_version < '4'", - "version": "==2.28.1" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" - }, - "snowballstemmer": { - "hashes": [ - "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", - "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" - ], - "version": "==2.2.0" - }, - "sphinx": { - "hashes": [ - "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d", - "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5" - ], - "index": "pypi", - "version": "==5.3.0" - }, - "sphinx-autobuild": { - "hashes": [ - "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac", - "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05" - ], - "index": "pypi", - "version": "==2021.3.14" - }, - "sphinx-rtd-theme": { - "hashes": [ - "sha256:31faa07d3e97c8955637fc3f1423a5ab2c44b74b8cc558a51498c202ce5cbda7", - "sha256:6146c845f1e1947b3c3dd4432c28998a1693ccc742b4f9ad7c63129f0757c103" - ], - "index": "pypi", - "version": "==1.1.1" - }, - "sphinx-sitemap": { - "hashes": [ - "sha256:3e19cff281974c00dfdc72d2d6ac71b737805dbd5666019997a4ac062a6406c0", - "sha256:94a1dab73789558e28e5ccfbc2a758194765707f9eeb06b59725a3bb244f323b" - ], - "index": "pypi", - "version": "==2.4.0" - }, - "sphinxcontrib-applehelp": { - "hashes": [ - "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", - "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.2" - }, - "sphinxcontrib-devhelp": { - "hashes": [ - "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", - "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.2" - }, - "sphinxcontrib-htmlhelp": { - "hashes": [ - "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07", - "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2" - ], - "markers": "python_version >= '3.6'", - "version": "==2.0.0" - }, - "sphinxcontrib-jsmath": { - "hashes": [ - "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", - "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.1" - }, - "sphinxcontrib-qthelp": { - "hashes": [ - "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", - "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.3" - }, - "sphinxcontrib-serializinghtml": { - "hashes": [ - "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", - "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952" - ], - "markers": "python_version >= '3.5'", - "version": "==1.1.5" - }, - "tornado": { - "hashes": [ - "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca", - "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72", - "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23", - "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8", - "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b", - "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9", - "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13", - "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75", - "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac", - "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e", - "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b" - ], - "markers": "python_version > '2.7'", - "version": "==6.2" - }, - "urllib3": { - "hashes": [ - "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc", - "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.13" - }, - "zipp": { - "hashes": [ - "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa", - "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766" - ], - "markers": "python_version >= '3.7'", - "version": "==3.11.0" - } - }, - "develop": {} -} diff --git a/docs/_ext/edit_on_github.py b/docs/_ext/edit_on_github.py deleted file mode 100644 index f9949daa5..000000000 --- a/docs/_ext/edit_on_github.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Sphinx extension to add ReadTheDocs-style "Edit on GitHub" links to the -sidebar. -""" - -import warnings - - -def html_page_context(app, pagename, templatename, context, doctree): - if templatename != 'page.html': - return - - if not app.config.edit_on_github_project: - warnings.warn("edit_on_github_project not specified") - return - - # For sphinx_rtd_theme. - context['display_github'] = True - context['github_user'] = app.config.edit_on_github_project.split('/')[0] - context['github_version'] = app.config.edit_on_github_branch + '/' - context['github_repo'] = app.config.edit_on_github_project.split('/')[1] - context['conf_py_path'] = 'docs/' - - -def setup(app): - app.add_config_value('edit_on_github_project', 'lingui/js-lingui', True) - app.add_config_value('edit_on_github_branch', 'main', True) - app.connect('html-page-context', html_page_context) diff --git a/docs/_ext/linguidocs.py b/docs/_ext/linguidocs.py deleted file mode 100644 index c3fdb7e65..000000000 --- a/docs/_ext/linguidocs.py +++ /dev/null @@ -1,109 +0,0 @@ -""" -Lingui docs extensions - -Inspired by Django Docs -https://github.com/django/django/blob/main/docs/_ext/djangodocs.py -""" - -from docutils import nodes -from sphinx import addnodes -from sphinx.domains.std import Cmdoption -from sphinx.locale import _ -from sphinx.util.docfields import TypedField - - -class jsxmacro(nodes.Inline, nodes.TextElement): - pass - - -def visit_react_macro_html(self, node): - self.body.append('<') - - -def depart_react_macro_html(self, node): - self.body.append('>') - - -class react_component(nodes.Inline, nodes.TextElement): - pass - - -def visit_react_component_html(self, node): - self.body.append('<') - - -def depart_react_component_html(self, node): - self.body.append('>') - - -class jsmacro(nodes.Inline, nodes.TextElement): - pass - - -def visit_jsmacro_html(self, node): - pass - - -def depart_jsmacro_html(self, node): - pass - - -def parse_lingui_cli_node(env, sig, signode): - command = sig.split(' ')[0] - env.ref_context['std:program'] = command - title = "lingui %s" % sig - signode += addnodes.desc_name(title, title) - return command - - -def setup(app): - app.add_object_type( - directivename='jsxmacro', - rolename='jsxmacro', - indextemplate="pair: %s; jsxmacro", - ref_nodeclass=jsxmacro, - objname='React macro', - doc_field_types=[ - TypedField('props', label=_('Props'), - names=('prop',), - typerolename='jsxmacro', - typenames=('proptype', 'type')), - ] - ) - app.add_node(jsxmacro, - html=(visit_react_macro_html, depart_react_macro_html)) - - app.add_object_type( - directivename='component', - rolename='component', - indextemplate="pair: %s; component", - ref_nodeclass=react_component, - objname='Component', - doc_field_types=[ - TypedField('props', label=_('Props'), - names=('prop',), - typerolename='component', - typenames=('proptype', 'type')), - ] - ) - app.add_node(react_component, - html=(visit_react_component_html, depart_react_component_html)) - app.add_object_type( - directivename='jsmacro', - rolename='jsmacro', - indextemplate="pair: %s; jsmacro", - ref_nodeclass=jsmacro, - objname='JS Macro' - ) - app.add_node(jsmacro, - html=(visit_jsmacro_html, depart_jsmacro_html)) - app.add_crossref_type('config', 'conf') - app.add_crossref_type('icu', 'icu') - - app.add_object_type( - directivename="lingui-cli", - rolename="cli", - indextemplate="pair: %s; lingui-cli command", - parse_node=parse_lingui_cli_node, - ) - app.add_directive('lingui-cli-option', Cmdoption) diff --git a/docs/_ext/youtube.py b/docs/_ext/youtube.py deleted file mode 100644 index efe6901c2..000000000 --- a/docs/_ext/youtube.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from __future__ import division - -import re -from docutils import nodes -from docutils.parsers.rst import directives, Directive - -CONTROL_HEIGHT = 30 - -def get_size(d, key): - if key not in d: - return None - m = re.match("(\d+)(|%|px)$", d[key]) - if not m: - raise ValueError("invalid size %r" % d[key]) - return int(m.group(1)), m.group(2) or "px" - -def css(d): - return "; ".join(sorted("%s: %s" % kv for kv in d.items())) - -class youtube(nodes.General, nodes.Element): pass - -def visit_youtube_node(self, node): - aspect = node["aspect"] - width = node["width"] - height = node["height"] - - if aspect is None: - aspect = 16, 9 - - if (height is None) and (width is not None) and (width[1] == "%"): - style = { - "padding-top": "%dpx" % CONTROL_HEIGHT, - "padding-bottom": "%f%%" % (width[0] * aspect[1] / aspect[0]), - "width": "%d%s" % width, - "position": "relative", - } - self.body.append(self.starttag(node, "div", style=css(style))) - style = { - "position": "absolute", - "top": "0", - "left": "0", - "width": "100%", - "height": "100%", - "border": "0", - } - attrs = { - "src": "https://www.youtube.com/embed/%s" % node["id"], - "style": css(style), - } - self.body.append(self.starttag(node, "iframe", **attrs)) - self.body.append("") - else: - if width is None: - if height is None: - width = 560, "px" - else: - width = height[0] * aspect[0] / aspect[1], "px" - if height is None: - height = width[0] * aspect[1] / aspect[0], "px" - style = { - "width": "%d%s" % width, - "height": "%d%s" % (height[0] + CONTROL_HEIGHT, height[1]), - "border": "0", - } - attrs = { - "src": "https://www.youtube.com/embed/%s" % node["id"], - "style": css(style), - } - self.body.append(self.starttag(node, "iframe", **attrs)) - self.body.append("") - -def depart_youtube_node(self, node): - pass - -class YouTube(Directive): - has_content = True - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = False - option_spec = { - "width": directives.unchanged, - "height": directives.unchanged, - "aspect": directives.unchanged, - } - - def run(self): - if "aspect" in self.options: - aspect = self.options.get("aspect") - m = re.match("(\d+):(\d+)", aspect) - if m is None: - raise ValueError("invalid aspect ratio %r" % aspect) - aspect = tuple(int(x) for x in m.groups()) - else: - aspect = None - width = get_size(self.options, "width") - height = get_size(self.options, "height") - return [youtube(id=self.arguments[0], aspect=aspect, width=width, height=height)] - -def setup(app): - app.add_node(youtube, html=(visit_youtube_node, depart_youtube_node)) - app.add_directive("youtube", YouTube) diff --git a/docs/_static/pitch.js b/docs/_static/pitch.js deleted file mode 100644 index a0c116088..000000000 --- a/docs/_static/pitch.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from "react" - -export default function Lingui({ numUsers, name = "You" }) { - return ( -
-

Internationalization in React

- - Logo of Lingui Project - -

- Hello {name}, LinguiJS is a readable, automated, and optimized (5 kb) - internationalization for JavaScript. -

- -

- Read the documentation - for more info. -

- - {numUsers === 1 ? ( - - Only one user is using this library! - - ) : ( - - {numUsers} users are using this library! - - )} -
- ) -} diff --git a/docs/_static/pitch_keys.js b/docs/_static/pitch_keys.js deleted file mode 100644 index 11e786608..000000000 --- a/docs/_static/pitch_keys.js +++ /dev/null @@ -1,53 +0,0 @@ -import React from "react" -import { t, Trans, Plural } from "@lingui/macro" - -export default function Lingui({ numUsers, name = "You" }) { - return ( -
-

- {/* Localized messages are simply wrapped in */} - Internationalization in React -

- - {/* Element attributes are translated using t macro */} - {t`Logo - -

- {/* Variables are passed to messages in the same way as in JSX */} - - Hello {name}, LinguiJS is a readable, automated, and optimized (5 kb) - internationalization for JavaScript. - -

- - {/* React Elements inside messages works in the same way as in JSX */} -

- - Read the documentation - for more info. - -

- - {/* - Plurals are managed using ICU plural rules. - Content of one/other slots is localized using . - Nesting of i18n components is allowed. - Syntactically valid message in ICU MessageFormat is guaranteed. - */} - - Only one user is using this library! - - } - other={ - - {numUsers} users are using this library! - - } - /> -
- ) -} diff --git a/docs/_static/pitch_keys.png b/docs/_static/pitch_keys.png deleted file mode 100644 index c3cfbcc62..000000000 Binary files a/docs/_static/pitch_keys.png and /dev/null differ diff --git a/docs/_static/pitch_messages.js b/docs/_static/pitch_messages.js deleted file mode 100644 index 528ca1a48..000000000 --- a/docs/_static/pitch_messages.js +++ /dev/null @@ -1,52 +0,0 @@ -import React from "react" -import { Trans, Plural } from "@lingui/macro" - -export default function Lingui({ numUsers, name = "You" }) { - return ( -
-

- {/* Localized messages are simply wrapped in */} - Internationalization in React -

- - {/* Element attributes are translated using t macro */} - {t`Logo - -

- {/* Variables are passed to messages in the same way as in JSX */} - - Hello {name}, LinguiJS is a readable, automated, and optimized (5 kb) - internationalization for JavaScript. - -

- -

- {/* Also React Elements inside messages works in the same way as in JSX */} - - Read the documentation - for more info. - -

- - {/* - Plurals are managed using ICU plural rules. - Content of one/other slots is localized using . - Nesting of i18n components is allowed. - Syntactically valid message in ICU MessageFormat is guaranteed. - */} - - Only one user is using this library! - - } - other={ - - {numUsers} users are using this library! - - } - /> -
- ) -} diff --git a/docs/_static/pitch_messages.png b/docs/_static/pitch_messages.png deleted file mode 100644 index 5a2f1a08e..000000000 Binary files a/docs/_static/pitch_messages.png and /dev/null differ diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html deleted file mode 100644 index 23abd8cb9..000000000 --- a/docs/_templates/layout.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "!layout.html" %} - -{% block extrahead %} -{{ super() }} - - -{% endblock %} diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 24469cd4f..000000000 --- a/docs/conf.py +++ /dev/null @@ -1,169 +0,0 @@ -# -*- coding: utf-8 -*- -# -# LinguiJS documentation build configuration file, created by -# sphinx-quickstart on Fri Sep 15 16:18:52 2017. -# -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import sys -import sphinx_rtd_theme -from os.path import abspath, join, dirname -sys.path.append(abspath(join(dirname(__file__), "_ext"))) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' - -primary_domain = 'js' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'linguidocs', - 'edit_on_github', - 'youtube', - 'sphinx_sitemap' -] - -# For sphinx_sitemap -site_url = 'https://lingui.js.org/' - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'LinguiJS' -copyright = '2018, Tomáš Ehrlich' -author = 'Tomáš Ehrlich' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '' -# The full version, including alpha/beta/rc tags. -release = '' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -html_theme = "sphinx_rtd_theme" -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# This is required for the alabaster theme -# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars -html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'relations.html', # needs 'show_related': True theme option to display - 'searchbox.html', - 'donate.html', - ] -} - -rst_epilog = """ -.. _LinguiJS: https://github.com/lingui/js-lingui -.. _CLDR Plurals: http://cldr.unicode.org/index/cldr-spec/plural-rules -.. _CLDR Plural Rules: http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html -""" - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. -htmlhelp_basename = 'LinguiJSDoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'LinguiJS.tex', u'LinguiJS Documentation', - u'Tomáš Ehrlich', 'manual'), -] - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'LinguiJS', u'LinguiJS Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'LinguiJS', u'LinguiJS Documentation', - author, 'LinguiJS', 'One line description of project.', - 'Miscellaneous'), -] diff --git a/docs/guides/dynamic-loading-catalogs.rst b/docs/guides/dynamic-loading-catalogs.rst deleted file mode 100644 index fb359ff56..000000000 --- a/docs/guides/dynamic-loading-catalogs.rst +++ /dev/null @@ -1,129 +0,0 @@ -.. _dynamic-loading-catalogs: - -Jump to `final helper `_. - -*********************************** -Dynamic loading of message catalogs -*********************************** - -``I18nProvider`` doesn't assume anything about your app and it's your -responsibility to load messages based on active language. Here's an example of a basic setup -with a dynamic load of catalogs. - -Setup -===== - -.. warning:: - - You don't have to install following Babel plugins if you're using Create React App - or similar framework which already has it. - -We are using the `Dynamic Import() Proposal `_ -to ECMAScript. We need to install ``@babel/plugin-syntax-dynamic-import`` and -``babel-plugin-dynamic-import-node`` to make it work. Also, the code examples given here make use of ``@babel/plugin-proposal-class-properties`` - -.. code-block:: shell - - yarn add --dev @babel/plugin-syntax-dynamic-import babel-plugin-dynamic-import-node @babel/plugin-proposal-class-properties - -.. warning:: - - `babel-plugin-dynamic-import-node` is required when running tests in Jest. - -.. code-block:: js - - // .babelrc - { - "plugins": [ - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-proposal-class-properties" - ], - "env": { - "test": { - "plugins": [ - "dynamic-import-node" - ] - } - } - } - -Final I18n loader helper -======================== - -Here's the full source of ``i18n.ts`` logic: - -.. code-block:: jsx - - import { i18n } from '@lingui/core'; - import { en, cs } from 'make-plural/plurals' - - export const locales = { - en: "English", - cs: "Česky", - }; - export const defaultLocale = "en"; - - i18n.loadLocaleData({ - en: { plurals: en }, - cs: { plurals: cs }, - }) - - /** - * We do a dynamic import of just the catalog that we need - * @param locale any locale string - */ - export async function dynamicActivate(locale: string) { - const { messages } = await import(`./locales/${locale}/messages`) - i18n.load(locale, messages) - i18n.activate(locale) - } - - -**How should I use the dynamicActivate in our application?** - -.. code-block:: jsx - - import React, { useEffect } from 'react'; - import App from './App'; - - import { I18nProvider } from '@lingui/react'; - import { i18n } from '@lingui/core'; - import { defaultLocale, dynamicActivate } from './i18n'; - - const I18nApp = () => { - useEffect(() => { - // With this method we dynamically load the catalogs - dynamicActivate(defaultLocale) - }, []) - - return ( - - - - ) - } - - -Conclusion -========== - -Looking at the content of build dir, we see one chunk per language: - -.. code-block:: shell - - i18n-0.c433b3bd.chunk.js - i18n-1.f0cf2e3d.chunk.js - main.ab4626ef.js - -When page is loaded initially, only main bundle and bundle for the first -language are loaded: - -.. image:: ./dynamic-loading-catalogs-1.png - :alt: Requests during the first render - -After changing language in UI, the second language bundle is loaded: - -.. image:: ./dynamic-loading-catalogs-2.png - :alt: Requests during the second render - -And that's it! 🎉 diff --git a/docs/guides/excluding-build-files.rst b/docs/guides/excluding-build-files.rst deleted file mode 100644 index f420bd48e..000000000 --- a/docs/guides/excluding-build-files.rst +++ /dev/null @@ -1,32 +0,0 @@ -Excluding message catalog build files -===================================== - -:cli:`extract` command creates temporary message catalogs per each source file. Also, -:cli:`compile` command generates compiled message catalogs from source ones. All these files -can be safely ignored from VCS and linters. - -Can be safely ignored because this files must be created every time you deploy to production, so we encourage to use CI methods to automatize this process. -If you commit it you will produce conflicts, which somebody will need to solve, in this minimized and transpired (basically unreadable to human) file. -In summary, please, **compile always your catalogs** - -Replace ``locales`` in paths below with your custom :conf:`localeDir` from configuration. - -- ``locales/_build/`` -- ``locales/**/*.js`` - -Git ---- - -Add following lines to your ``.gitignore``:: - - locales/_build/ - locales/**/*.js - -ESLint ------- - -Specify which directories to lint explicitely or add following lines to your -`.eslintignore `_:: - - locales/_build/ - locales/**/*.js diff --git a/docs/guides/monorepo.rst b/docs/guides/monorepo.rst deleted file mode 100644 index 0cd007366..000000000 --- a/docs/guides/monorepo.rst +++ /dev/null @@ -1,26 +0,0 @@ -************* -Monorepo -************* - -If you're using lingui within a monorepo, you need to pass some extra options to ``lingui`` babel. -``{ rootMode: "upward" }`` is required to ``lingui`` find the correct babel config - -.. code-block:: json - - { - "catalogs": [{ - "path": "/locale/{locale}/messages", - "include": [""], - "exclude": ["**/node_modules/**"] - }], - "extractBabelOptions": { - "rootMode": "upward", - }, - "format": "po", - "locales": ["en"], - } - -In summary, we'll have: - - 1x ``babel.config.js`` within root - - 1x ``lingui.config.js`` within root - - And **n** ``lingui.config.js`` per package which extends/overrides from root diff --git a/docs/guides/optimized-components.rst b/docs/guides/optimized-components.rst deleted file mode 100644 index 2f14fab05..000000000 --- a/docs/guides/optimized-components.rst +++ /dev/null @@ -1,123 +0,0 @@ -.. _guide-optimized-components: - -******************** -Optimized components -******************** - -Let's call any component implementing ``shouldComponentUpdate`` an *Optimized -component*. - -.. hint:: - - **TL;DR:** Optimized components are handled correctly in LinguiJS by default. - Read this document if you want to understand how things work under the hood. - -React components can be optimized to skip updates implementing -``shouldComponentUpdate``. Based on change of props and state, component -can decide to continue re-rendering or skip the update completely. -However, LinguiJS reads translations from context and there are two cases -which must be handled to make i18n related updates reliable. - -The two cases to handle are: - -1. Translations inside optimized component. -2. Optimized component is wrapped in :js:func:`withI18n` to translate text attributes. - -Let's take a look at both scenarios. - -Translations inside optimized component -======================================= - -Imagine following React tree:: - - - - I am not alone! - - - -When active language is changed or message catalog is updated, -``OptimizedComponent`` will probably skip the update, because it's props -don't change. It means that all children of ``OptimizedComponent`` won't be -updated, including :component:`Trans` component. - -By default, all :component:`Trans` components listen for language and catalog changes -and update themselves when it happens. Even if ``OptimizedComponent`` -skips update, :component:`Trans` component is updated correctly. - -Also, :js:func:`withI18n` HOC listens for language and catalog changes, but this -behavior can be disabled by passing ``update = false`` option: - -.. code-block:: jsx - - // Component won't listen for language/catalog changes - export default withI18n({ update = false })(Component) - -Optimized component wrapped in :js:func:`withI18n` -================================================== - -Component should be wrapped in :js:func:`withI18n` HOC when it's required to access -low-level i18n API. Common usecase is translation of attributes: - -.. code-block:: jsx - - import * as React from 'react' - import { t, Trans } from '@lingui/macro' - - class HeaderLink extends React.PureComponent { - render () { - return Header - } - } - - export default withI18n()(HeaderLink) - -Content of link will be updated correctly as discussed in previous section. -However, text attributes aren't components but only function calls so they can't -listen to changes of active language and catalog. - -The trick here is to update whole component, but since it's a PureComponent, -it does shallow comparison of props. :js:func:`withI18n` HOC makes things easier by -passing ``i18nHash`` to wrapped component. This hash is changed after every -change of active language or catalog. - -If you have your own implementation of ``shouldComponentUpdate``, simply compare -also ``i18nHash``: - -.. code-block:: jsx - - import * as React from 'react' - - class HeaderLink extends React.Component { - shouldComponentUpdate(nextProps, nextState) { - return nextProps.i18nHash !== this.props.i18nHash /* && your condition */ - } - - render () { - // render component as usual - } - } - -If you don't want your component to receive this hash for whatever reason, -you can disable it by passing ``withHash = false`` option to HOC: - -.. code-block:: jsx - - // Component won't pass i18nHash prop - export default withI18n({ withHash = false })(Component) - -Summary -======= - -LinguiJS handles updates in and for Optimized components in most cases. If you -want to disable this behavior, you can pass either ``update = false`` -or ``withHash = false`` to :js:func:`withI18n` HOC. - -``update`` fixes updates if component has optimized parents while ``withHash`` -fixes updates for intermediate optimized children. - -Further reading -=============== - -- `React docs: Optimizing Performance `_ -- `How to handle React context in a reliable way `_ diff --git a/docs/guides/plurals.rst b/docs/guides/plurals.rst deleted file mode 100644 index 7de870a0c..000000000 --- a/docs/guides/plurals.rst +++ /dev/null @@ -1,112 +0,0 @@ -************* -Pluralization -************* - -Plurals are essential when dealing with internationalization. LinguiJS_ -uses `CLDR Plural Rules`_. In general, there are 6 plural forms (taken -from `CLDR Plurals`_ page): - -- zero -- one (singular) -- two (dual) -- few (paucal) -- many (also used for fractions if they have a separate class) -- other (required—general plural form—also used if the language only has a single form) - -Only the last one, *other*, is required because it's the only common plural form -used in all languages. - -All other plural forms depends on language. For example English has only two: -*one* and *other* (1 book vs. 2 books). In Czech, we have three: *one*, *few*, -*many* (1 kniha, 2 knihy, 5 knih). Some languages have even more, like Russian -and Arabic. - -Using plural forms -================== - -Good thing is that **as developers, we have to know only plural forms for -the source language**. - -If we use English in the source code, then we'll use only *one* and *other*: - -.. code-block:: js - - i18n.plural({ - value: numBooks, - one: "# book", - other: "# books" - }) - -When ``numBooks == 1``, this will render as *1 book* and for ``numBook == 2`` -it will be *2 books*. - - Funny fact for non-english speakers: In English, 0 uses plural form too, - *0 books*. - -Under the hood, ``i18n.plural`` is replaced with low-level ``i18n._``. For production, the above example will become: - -.. code-block:: js - - i18n._('{numBooks, plural, one {# book} other {# books}}', { numBooks }) - -When we extract messages from source code using :doc:`lingui-cli `, we get: - -.. code-block:: default - - {numBooks, plural, one {# book} other {# books}} - -Now, we give it to our Czech translator and they'll translate it as: - -.. code-block:: default - - {numBooks, plural, one {# kniha} few {# knihy} other {# knih}} - -The important thing is that *we don't need to change our code to support -languages with different plural rules*. Here's a step-by-step description of -the process: - -1. In source code, we have: - - .. code-block:: js - - i18n.plural({ - value: numBooks, - one: "# book", - other: "# books" - }) - -2. Code is compiled to (using `lingui-js` or `lingui-react` babel preset): - - .. code-block:: js - - i18n._( - '{numBooks, plural, one {# book} other {# books}}', - { numBooks } - ) - -3. Message `{numBooks, plural, one {# book} other {# books}}` is translated to: - - .. code-block:: default - - {numBooks, plural, one {# kniha} few {# knihy} other {# knih}} - -4. Finally, message is formatted using Czech plural rules. - -Using other language than English -================================= - -That works perfectly fine! Just learn what `plural forms `_ your -languages has and then you can use them. Here's the example in Czech: - -.. code-block:: js - - i18n.plural({ - value: numBooks, - one: '# kniha', - few: '# knihy', - other: '# knih' - }) - -This make LinguiJS_ useful also for unilingual projects, i.e: if you don't -translate your app at all. Plurals, number and date formatting are common in -every language. diff --git a/docs/guides/pseudolocalization.rst b/docs/guides/pseudolocalization.rst deleted file mode 100644 index 260af04f6..000000000 --- a/docs/guides/pseudolocalization.rst +++ /dev/null @@ -1,57 +0,0 @@ -================== -Pseudolocalization -================== - -There is built in support for `pseudolocalization `. -Pseudolocalization is a method for testing the internationalization aspects -of your application by replacing your strings with altered versions -and maintaining string readability. It also makes hard coded strings -and improperly concatenated strings easy to spot so that they can be properly localized. - - Example: - Ţĥĩś ţēxţ ĩś ƥśēũďōĺōćàĺĩźēď - -Configuration -============= - -To setup pseudolocalization add :conf:`pseudoLocale` to your lingui :doc:`configuration file `:: - - { - "lingui": { - "locale": ["en", "pseudo-LOCALE"], - "pseudoLocale": "pseudo-LOCALE" - "fallbackLocales": { - "pseudo-LOCALE": "en" - } - } - } - -:conf:`pseudoLocale` option can be any string that is in :conf:`locale` - -Examples: :conf:`en-PL`, :conf:`pseudo-LOCALE`, :conf:`pseudolocalization` or :conf:`en-UK` - -Create pseudolocalization -========================= - -PseudoLocale string have to be in :conf:`locales` config as well. -Otherwise no folder and no pseudolocalization is going to be created. -After running ``yarn extract`` verify that the folder has been created. -The pseudolocalization is automatically created on ``yarn compile`` from messages -in order specified in `in this cli section <../tutorials/cli.html#preparing-catalogs-for-production>`_. -In case fallbackLocales has been used, the pseudolocalization is going to be created from translated fallbacklocale. - -Switch browser into specified pseudoLocale -====================================================== - -We can use browsers settings or extensions. Extensions allow to use any locale. -Browsers are usually limited into valid language tags (BCP 47). -In that case, the locale for pseudolocalization has to be standard locale, -which is not used in your application for example :conf:`zu_ZA` Zulu - SOUTH AFRICA - -Chrome: -a) With extension (valid locale) - https://chrome.google.com/webstore/detail/locale-switcher/kngfjpghaokedippaapkfihdlmmlafcc -b) Without extension (valid locale) - chrome://settings/?search=languages - -Firefox: -a) With extension (any string) - https://addons.mozilla.org/en-GB/firefox/addon/quick-accept-language-switc/?src=search -b) Without extension (valid locale) - about:preferences#general > Language diff --git a/docs/guides/testing.rst b/docs/guides/testing.rst deleted file mode 100644 index e5094c325..000000000 --- a/docs/guides/testing.rst +++ /dev/null @@ -1,60 +0,0 @@ -Testing -======= - -Components using :component:`Trans`, :js:func:`withI18n` or :js:func:`useLingui` require access to the context of :component:`I18nProvider`. How you can wrap your component with the I18nProvider depends on the test library you use. - -Here is a working example with `react-testing-library`_, using the `wrapper-property`_: - -.. _`react-testing-library` : https://testing-library.com/docs/react-testing-library/intro -.. _wrapper-property: https://testing-library.com/docs/react-testing-library/api#wrapper - - -.. code-block:: jsx - - // index.js - import React from 'react' - import { getByText, render, act } from '@testing-library/react' - import { i18n } from '@lingui/core' - import { I18nProvider } from '@lingui/react' - import { en, cs } from 'make-plural/plurals' - - import { messages } from './locales/en/messages' - import { messages as csMessages } from './locales/cs/messages' - import App from './App' - - i18n.load({ - en: messages, - cs: csMessages - }) - i18n.loadLocaleData({ - en: { plurals: en }, - cs: { plurals: cs } - }); - - const TestingProvider = ({ children }: any) => ( - - {children} - - ) - - test('Content should be translated correctly in English' , () => { - act(() => { - i18n.activate('en') - }) - const { getByTestId, container } = render(, { wrapper: TestingProvider }); - expect(getByTestId('h3-title')).toBeInTheDocument() - expect(getByText(container, "Language switcher example:")).toBeDefined() - }); - - test('Content should be translated correctly in Czech', () => { - act(() => { - i18n.activate('cs') - }) - const { getByTestId, container } = render(, { wrapper: TestingProvider }); - expect(getByTestId('h3-title')).toBeInTheDocument() - expect(getByText(container, "Příklad přepínače jazyků:")).toBeDefined() - }); - -You could define a custom renderer to re-use this TestingProvider, see `react testing library - Custom Render`_ - -.. _`react testing library - Custom Render`: https://testing-library.com/docs/react-testing-library/setup#custom-render diff --git a/docs/guides/typescript.rst b/docs/guides/typescript.rst deleted file mode 100644 index 03a92fb5c..000000000 --- a/docs/guides/typescript.rst +++ /dev/null @@ -1,106 +0,0 @@ -********** -Typescript -********** - -Lingui supports typescript types out of the box since version ``3.0.0``. Feel free to submit any query you find related to typescript on Github Issues - -Webpack setup -============= -The ability of lingui to support the intuitive syntax comes from the ``@lingui/babel-preset-react`` Babel transformation. The preset consist of 2 plugins, namely ``@lingui/babel-plugin-transform-js`` and ``@lingui/babel-plugin-transform-react``. The plugins perform transformation only on the JSX and tagged template literals. Thus, the JSX and tagged template literals *must not* be transpiled before the 2 plugins get to do their magic to process the intuitive syntax. - -In order to preserve JSX and tagged template literals for the lingui plugins, you must set the following in your ``tsconfig.json``. - -.. code:: js - - { - "compilerOptions": { - "jsx": "preserve", - "target": "es2016" - } - } - -For lingui 2.0+, install ``babel-loader``, ``babel-preset-react``, ``babel-preset-env``, ``@lingui/babel-preset-react``. Use the presets by changing your ``.babelrc`` to the following. The order of the preset is important. - -.. code:: js - - { - "presets": [ - "babel-preset-env", - "babel-preset-react", - "@lingui/babel-preset-react" - ] - } - -In your ``webpack.config.js``, use both ``babel-loader`` and ``ts-loader`` for Typescript files. - -.. code:: js - - { - module: { - rules: [ - { - test: /\.tsx?$/, - exclude: /node_modules/, - use: ['babel-loader', 'ts-loader'] - } - ] - } - } - -.. note:: - - If you are not using `.babelrc` file, keep in mind that by running `lingui extract`, the Webpack config is not used. - To supply babel options for the extraction process use :conf:`extractBabelOptions` configuration option. - - -:conf:`compileNamespace` must be set to ``ts`` (ES6 default export) in the Lingui config -otherwise compiled catalogs can't be imported using ES ``import``, but rather CommonJS ``require``: - -.. code:: js - - { - "compileNamespace": "ts" - } - - -Macros types in non-React environments -====================================== - -Since the opening of this issue we investigated that macros can be used on Typescript environments where React isn't required. - -Now we're shipping two declaration types: - - ``index.d.ts`` files with ``@lingui/core``, ``@lingui/react`` and ``react`` as peerDependencies. - - ``global.d.ts`` files with just ``@lingui/core`` as peerDependencies. - -Now you can modify your ``tsconfig.json`` in your root directory and reference the global file: - -.. code-block:: json - - { - "compilerOptions": { - "types": [ - "./node_modules/@lingui/macro/global", - ] - } - } - - -Type definitions -================ - -Since version ``3.0.0`` types are already inside ``@lingui`` modules, so you don't need to install any external dependency related to types. - -**For earlier versions**: - -`Jeow Li Huan `_ wrote type definition for ``@lingui/core`` -and ``@lingui/react``: - -The type definitions requires Typescript 2.8 or later. - -.. code-block:: shell - - npm install --save-dev @types/lingui__core # types for @lingui/core - npm install --save-dev @types/lingui__react # types for @lingui/react - npm install --save-dev @types/lingui__macro # types for @lingui/macro - -Please report any issues in `maintainers repo `_. diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 7934f0b25..000000000 --- a/docs/index.rst +++ /dev/null @@ -1,152 +0,0 @@ -****************************************************** -LinguiJS - Seamless internationalization in Javascript -****************************************************** - -.. important:: This documentation is for the latest version Lingui 3.x - - Old documentation is available at https://js-lingui-git-stable-2x.lingui-js.vercel.app/ - -🌍📖 A readable, automated, and optimized (5 kb) internationalization for JavaScript - - **Internationalization** is the design and development of a product, application - or document content that enables easy **localization** for target audiences that - vary in culture, region, or language. - - --- `W3C Web Internationalization FAQ `_ - -.. image:: https://img.shields.io/github/stars/lingui/js-lingui.svg?style=social&label=Stars - :alt: GitHub stars - :target: https://github.com/lingui/js-lingui/ - -.. image:: https://img.shields.io/github/watchers/lingui/js-lingui.svg?style=social&label=Follow - :alt: GitHub followers - :target: https://github.com/lingui/js-lingui/ - -Key features -============ - -Lingui is an easy yet powerful internationalization framework for global projects. - -Clean and readable - Keep your code clean and readable, while the library uses - battle-tested and powerful **ICU MessageFormat** under the hood. - -Universal - Use it everywhere. :ref:`@lingui/core ` provides the essential intl - functionality which works in any JavaScript project while :ref:`@lingui/react ` offers - components to leverage React rendering. - -Full rich-text support - Use React components inside localized messages - without any limitation. Writing rich-text messages is as easy as writing JSX. - -Powerful tooling - Manage the whole intl workflow using Lingui :ref:`CLI `. It - extracts messages from source code, validates messages coming from translators and - checks that all messages are translated before shipping to production. - -Unopinionated - Integrate Lingui into your existing workflow. It supports - message keys as well as auto generated messages. Translations are stored either in - JSON or standard PO file, which is supported in almost all translation tools. - -Lightweight and optimized - Core library is only `1.9 kB gzipped `_, - React components are additional `3.1 kBs gzipped `_. - That's less than Redux for a full-featured intl library. - -Active community - Join us on `Discord`_ to discuss the latest development. - At the moment, Lingui is the most active intl project on GitHub. - -Compatible with react-intl - Low-level React API is very similar to react-intl - and the message format is the same. It's easy to migrate existing project. - -Quick overview -============== - -.. literalinclude:: _static/pitch_keys.js - :language: jsx - -Documentation contents -====================== - -.. toctree:: - :maxdepth: 1 - :caption: Installation - - Create React App - React project - -.. toctree:: - :maxdepth: 1 - :caption: Tutorials - - React - React - common patterns - React Native - JavaScript - CLI - -.. toctree:: - :maxdepth: 1 - :caption: Guides - - Testing - Typescript - Excluding build files - Dynamic loading of translations - Optimized components - How plurals work - Lingui within monorepo - Pseudolocalization - -.. toctree:: - :maxdepth: 1 - :caption: Sync & Collaboration Tools - - Introduction - Translation.io - Crowdin - -.. toctree:: - :maxdepth: 1 - :caption: API References - - Core (JavaScript) - React - Macros - CLI - Configuration - Locale Detection - Webpack Loader - Snowpack Plugin - Catalog Formats - ICU MessageFormat - -.. toctree:: - :maxdepth: 1 - :caption: Releases - - Migration from 2.x to 3.x - Migration from 1.x to 2.x - Migration from 0.x to 1.x - -.. toctree:: - :maxdepth: 1 - :caption: Discussion - - Projects using LinguiJS - Comparison with react-intl - Talks and articles about i18n in JavaScript - Scripts, tools and services related to LinguiJS - - -Indices -------- - -* :ref:`genindex` -* :ref:`search` - -.. _Discord: https://discord.gg/gFWwAYnMtA diff --git a/docs/misc/react-intl.rst b/docs/misc/react-intl.rst deleted file mode 100644 index ee991d8ff..000000000 --- a/docs/misc/react-intl.rst +++ /dev/null @@ -1,290 +0,0 @@ -************************** -Comparison with react-intl -************************** - -`react-intl`_ is definitely the most popular and widely-used i18n library for React. -`LinguiJS`_ is in many ways very similar: both libraries use the same syntax for -messages (ICU MessageFormat) and they also have very similar API. - -Here's an example from `react-intl`_ docs: - -.. code-block:: jsx - - {name}, unreadCount}} - /> - -Looking at the low-level API of `LinguiJS`_, there isn't much difference: - -.. code-block:: jsx - - {name}, unreadCount}} - /> - -There's really no reason to reinvent the wheel when both libs are build on top of the -same message syntax. The story doesn't end here, though. - -Translations with rich-text markup -================================== - -Suppose we have the following text: - -.. code-block:: html - -

Read the documentation.

- -In `react-intl`_, this would be translated as: - -.. code-block:: jsx - - {chunks} - }} - /> - -`LinguiJS`_ extends ICU MessageFormat with tags. The example above would be: - -.. code-block:: jsx - - - ]} - /> - -and the translator gets the message in one piece: ``Read the <0>documentation``. - -However, let's go yet another level deeper. - -Macros for component-based message syntax -========================================= - -`LinguiJS`_ provides macros ``@lingui/macro`` which automatically generates a message -syntax. - -Let's go back to the previous example: - -.. code-block:: html - -

- Read the documentation. -

- -All we need to do is to wrap the message in a :jsxmacro:`Trans` macro: - -.. code-block:: html - -

- Read the documentation. -

- -The macro then parses the :jsxmacro:`Trans` macro children and generates -``defaults`` and ``components`` props automatically in the form described in the previous section. - -This is extremely useful when adding i18n to an existing project. All we need is to wrap -all messages in :jsxmacro:`Trans` macro. - -Let's compare it with `react-intl`_ solution to see the difference: - -.. code-block:: jsx - -

- {chunks} - }} - /> -

- -.. note:: - - It' also worth mentioning that the message IDs are completely optional. - `LinguiJS`_ is unopinionated in this way and perfectly works with messages as IDs as - well: - - .. code-block:: html - -

- Read the documentation. -

- - The message ID is ``Read the <0>documentation.`` instead of ``msg.docs``. Both - solutions have pros and cons and the library lets you choose the one which works best for you. - -Plurals -======= - -Another very common linguistic feature is pluralization. - -Let's take a look at the original example from `react-intl`_ docs: - -.. code-block:: jsx - - {name}, unreadCount}} - /> - -Using `LinguiJS`_ macros, we could combine :jsxmacro:`Trans`, :jsxmacro:`Plural` components and -:jsmacro:`number` macro: - -.. code-block:: jsx - - - Hello {name}, you have {number(undreadCount)} - - -and the final message would be very similar: - -.. code-block:: jsx - - {name}, you have {unreadCount, number} {unreadCount, plural, - one {message} - other {messages} - }`} - values={{name, unreadCount}} - /> - -The only difference is the `<0>` tag included in the message, as `LinguiJS`_ can handle -components in both variables and the message itself. - -.. note:: - - It's good to mention here that this isn't the best example of using plurals. - Make your translators happy and move plurals to the top of the message: - - .. code-block:: jsx - - Hello {name}, you have {number(undreadMessages)} message.} - other={<>Hello {name}, you have {number(undreadMessages)} messages.} - /> - - Even though both variants are syntactically valid in ICU MessageFormat, the second - one is easier for translating, because (again) the translator gets the phrase in one - piece. - -Text attributes -=============== - -Components can't be used in some contexts, e.g. to translate text attributes. -Whereas `react-intl`_ provides JS methods (e.g: ``formatMessage``) which return plain -strings, `LinguiJS`_ offers its core library for such translations. And it also provides -macros for these usecases! - -Here are a few short examples: - -.. code-block:: jsx - - {name} - {i18n._(plural({ - -Custom IDs are supported as well: - -.. code-block:: jsx - - {name} - {i18n._(plural("img.alt", - -.. note:: - - To inject ``i18n`` object into props, you need to use HOC :js:meth:`withI18n`. It's - very similar to ``injectIntl`` from `react-intl`_. Alternatively, you can also use - :component:`I18n` render prop component. - -External message catalog -======================== - -Let's say our app has been internationalized and we now want to send the messages -to the translator. - -`react-intl`_ comes with the Babel plugin which extracts messages from individual files, -but it's up to you to merge them into one file which you can send to translators. - -`LinguiJS`_ provides handy :doc:`CLI <../tutorials/cli>` which extracts messages and merges -them with any existing translations. Again, the story doesn't end here. - -Compiling messages -================== - -The biggest and slowest part of i18n libraries are message parsers and formatters. -`LinguiJS`_ compiles messages from MessageFormat syntax into JS functions which only -accept values for interpolation (e.g. components, variables, etc). This makes the -final bundle smaller and makes the library faster. The compiled catalogs are also bundled with locale -data like plurals, so it's not necessary to load them manually. - -Disadvantages of LinguiJS -========================= - -`react-intl`_ has been around for some time and it's definitely more popular, more used -and a lot of production sites are running it. The community is larger and it's much -easier to find help on StackOverflow and other sites. - -`LinguiJS`_ is a very new library and the community is very small at the moment. It's -not tested on many production sites. On the other hand, `LinguiJS`_'s testing suite -is very large and all examples are incorporated into the integration testing suite to make sure -everything is working fine. - -Last but not least, `LinguiJS`_ is actively maintained. Bugs are fixed regularly and new -features are constantly coming in. Work is currently progressing on -webpack code splitting, so that only messages related to the code in the chunk are loaded. - -Summary -======= - -- both libraries use the same MessageFormat syntax -- similar API (easy to port from one to the other) - -On top of that, `LinguiJS`_: - -- supports rich-text messages -- provides macros to simplify writing messages using MessageFormat syntax -- provides a CLI for extracting and compiling messages -- is very small (<5kb gzipped) and fast -- works also in vanilla JS (without React) -- is actively maintained - -On the other hand, `react-intl`_: - -- is the most popular and used i18n lib in React -- is used in many production websites (stability) -- has lots of resources available online - -Discussion -========== - -Do you have any comments or questions? Please join the discussion at -`Discord `_ or raise an -`issue `_. All feedback is welcome! - -.. _react-intl: https://github.com/yahoo/react-intl -.. _LinguiJS: https://github.com/lingui/js-lingui diff --git a/docs/misc/showroom.rst b/docs/misc/showroom.rst deleted file mode 100644 index 61af941b4..000000000 --- a/docs/misc/showroom.rst +++ /dev/null @@ -1,23 +0,0 @@ -Projects using LinguiJS -======================= - -Feel free to `send a PR `__ to list your project here. - -- `ledgy.com `__ (`source `__) -- `MyMusicTaste `__ -- `Caliopen `__ (`source `__) -- `Staycation `__ -- `Monitora `__ -- `Turisto `__ -- `Nolt `__ -- `easyname `__ -- `LocalEthereum `__ -- `Upcount `__ -- `Symbolovník `__ -- `Notos `__ -- `Greenlight `__ -- `OkCupid `__ -- `mapflow.ai `__ -- `Ascendero.com `__ -- `InterMenu.com `__ -- `Fider `__ (`source `__) diff --git a/docs/misc/talks-about-i18n.rst b/docs/misc/talks-about-i18n.rst deleted file mode 100644 index 0e83cfe0b..000000000 --- a/docs/misc/talks-about-i18n.rst +++ /dev/null @@ -1,45 +0,0 @@ -******************************************* -Talks and articles about i18n in JavaScript -******************************************* - -Articles -======== - -- `Javascript i18n with Lingui `_ - by `Mike Williamson `_ - -Talks -===== - -Internationalization is a piece of cake by Eli Schutze ------------------------------------------------------- - -*Published on 30. 10. 2018 by* EmpireJS_ - -.. youtube:: vhUiL_wUAjo - -| - -Let React speak your language by Tomáš Ehrlich ----------------------------------------------- - -*Published on 27. 10. 2018 by* `React Conf`_ - -.. youtube:: soAEB7ltQPk - -| - -React + i18n - You Have No Excuse! by Eli Schutze -------------------------------------------------- - -*Published on 20. 3. 2018 by* ReactFest_ - -.. youtube:: 6NwgHUcom_8 - -`Slides `_ - -| - -.. _React Conf: https://conf.reactjs.org/ -.. _EmpireJS: https://www.empirejs.org -.. _ReactFest: https://reactfest.uk/ diff --git a/docs/misc/tooling.rst b/docs/misc/tooling.rst deleted file mode 100644 index e19f1b9c4..000000000 --- a/docs/misc/tooling.rst +++ /dev/null @@ -1,11 +0,0 @@ -******* -Tooling -******* - -.. csv-table:: - :header: "Tool", "Type", "Description" - :widths: 15, 10, 30 - - "`storybook-addon-linguijs `_", storybook, "Storybook addon to provide language and locale switcher in storybook" - "`Auto Transation Tool `_", service, "Tool for translating JSON files for LinguiJS" - "`simpleen.io `_", service, "Online and CLI Tool to machine translate LinguiJS JSON files" diff --git a/docs/ref/catalog-formats.rst b/docs/ref/catalog-formats.rst deleted file mode 100644 index 40cd06772..000000000 --- a/docs/ref/catalog-formats.rst +++ /dev/null @@ -1,138 +0,0 @@ -*************** -Catalog formats -*************** - -Catalog format (configured by :conf:`format` option) refers to file format of -offline catalog. This format is never used in production, because it's compiled -into JS module. The reason behind this build step is that choice of catalog -format depends on individual internationalization workflow. On the other hand -runtime catalog should be as simple as possible so it parsed quickly without -additional overhead. - -PO File (recommended) -===================== - -PO files are translation files used by gettext_ internationalization system. -This format is recommended and in LinguiJS v3 it'll be the default catalog format. - -The advantages of this format are: - -- readable even for large messages -- supports comments for translators -- supports metadata (origin, flags) -- standard format supported by many localization tools - -.. code-block:: po - - #: src/App.js:3 - #. Comment for translators - msgid "messageId" - msgstr "Translated Message" - - #: src/App.js:3 - #, obsolete - msgid "obsoleteId" - msgstr "Obsolete Message" - -.. _gettext: https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html - -.. _po-gettext: - -PO File with gettext Plurals -============================ - -When using localization backends that don't understand the ICU plural syntax exported by the default `po` formatter, -**po-gettext** can be used to read and write to PO files using gettext-native plurals. - -This is how the regular PO format exports plurals: - -.. code-block:: po - - msgid "{count, plural, one {Message} other {Messages}}" - msgstr "{count, plural, one {Message} other {Messages}}" - -With `po-gettext`, plural messages are exported in the following way, depending on wheter an explicit ID is set: - -1. Message **with custom ID "my_message"** that is pluralized on property "someCount". - - Notice that 'msgid_plural' was generated by appending a '_plural' suffix. - - .. code-block:: po - - #. js-lingui:pluralize_on=someCount - msgid "my_message" - msgid_plural "my_message_plural" - msgstr[0] "Singular case" - msgstr[1] "Case number {someCount}" - -2. Message **without custom ID** that is pluralized on property "anotherCount". - - Notice how 'msgid' and 'msgid_plural' were extracted from original message. - - To allow matching this PO item to the appropriate catalog entry when deserializing, - the original ICU message is also stored in the generated comment. - - .. code-block:: po - - #. js-lingui:icu=%7BanotherCount%2C+plural%2C+one+%7BSingular+case%7D+other+%7BCase+number+%7BanotherCount%7D%7D%7D&pluralize_on=anotherCount - msgid "Singular case" - msgid_plural "Case number {anotherCount}" - msgstr[0] "Singular case" - msgstr[1] "Case number {anotherCount}" - -Note that this format comes with several caveats and should therefore only be used if using ICU plurals in PO files is -not an option: - - - Nested/multiple plurals in one message as shown in :jsmacro:`plural` are not supported as they cannot be expressed - with gettext plurals. Messages containing nested/multiple formats will not be output correctly. - - - :jsmacro:`select` and :jsmacro:`selectOrdinal` cannot be expressed with gettext plurals, but the original ICU format - is still saved to the `msgid`/`msgstr` properties. To disable the warning that this might not be the expected - behavior, include :code:`{ disableSelectWarning: true }` in the :conf:`formatOptions`. - - - Source/development languages with more than two plurals could experience difficulties when no custom IDs are used, - as gettext cannot have more than two plurals cases identifying an item (:code:`msgid` and :code:`msgid_plural`). - - - Gettext doesn't support plurals for negative and fractional numbers even though some languages have special rules - for these cases. - - -JSON -==== - -Simple JSON file where each key is message ID and value is translation. The JSON -is flat and there's no reason to use nested keys. The usual motivation behind nested -JSON is to save filespace, but this file format is used offline only. - -The drawback of this format is that all metadata about message are lost. That includes -default message, origin of message and any message flags (obsolete, fuzzy, etc). - -.. code-block:: json - - { - "messageId": "translation" - } - -Lingui (raw) -============ - -This file format simply outputs all internal data in JSON format. It's the original -file format used by LinguiJS library before support for other catalog formats were added. -It might be useful for tools build on top of Lingui CLI which needs to further -process catalog data. - -.. code-block:: json - - { - "messageId": { - "translation": "Translated message", - "defaults": "Default message", - "description": "Comment for translators", - "origin": [["src/App.js", 3]] - }, - "obsoleteId": { - "translation": "Obsolete message", - "origin": [["src/App.js", 3]], - "obsolete": true - } - } diff --git a/docs/ref/cli.rst b/docs/ref/cli.rst deleted file mode 100644 index 0fdeb0da5..000000000 --- a/docs/ref/cli.rst +++ /dev/null @@ -1,170 +0,0 @@ -.. _ref-cli: - -********************************* -API Reference - CLI (@lingui/cli) -********************************* - -``@lingui/cli`` manages locales, extracts messages from source files into -message catalogs and compiles message catalogs for production use. - - -Install -======= - -1. Install ``@lingui/cli`` as a development dependency: - - .. code-block:: shell - - npm install --save-dev @lingui/cli @babel/core - # Or yarn - yarn add --dev @lingui/cli @babel/core - -2. Add following scripts to your ``package.json``: - - .. code-block:: json - - { - "scripts": { - "extract": "lingui extract", - "compile": "lingui compile", - } - } - -Global options -============== - -.. lingui-cli-option:: --config - -Path to LinguiJS configuration file. If not set, the default file -is loaded as described in :doc:`LinguiJS configuration
` reference. - -Commands -======== - -``extract`` ------------ - -.. lingui-cli:: extract [files...] [--clean] [--overwrite] [--format ] [--locale ] [--convert-from ] [--verbose] [--watch [--debounce ]] - -This command extracts messages from source files and creates a message catalog for -each language using the following steps: - -1. Extract messages from all ``*.jsx?`` files inside :conf:`srcPathDirs` -2. Merge them with existing catalogs in :conf:`localeDir` (if any) -3. Write updated message catalogs to :conf:`localeDir` - -.. lingui-cli-option:: [files...] - -Filters source paths to only extract messages from passed files. -For ex: - - .. code-block:: shell - - lingui extract src/components - -Will extract only messages from ``src/components/**/*`` files, you can also pass multiple paths. - -It's useful if you want to run extract command on files that are staged, using for example ``husky``, before commiting will extract messages from staged files: - - .. code-block:: json - - { - "husky": { - "hooks": { - "pre-commit": "lingui extract $(git diff --name-only --staged)" - } - } - } - -.. lingui-cli-option:: --clean - -Remove obsolete messages from catalogs. Message becomes obsolete -when it's missing in the source code. - -.. lingui-cli-option:: --overwrite - -Update translations for :conf:`sourceLocale` from source. - -.. lingui-cli-option:: --format - -Format of message catalogs (see :conf:`format` option). - -.. lingui-cli-option:: --locale - -Only extract data for the specified locale. - -.. lingui-cli-option:: --convert-from - -Convert message catalogs from previous format (see :conf:`format` option). - -.. lingui-cli-option:: --verbose - -Prints additional information. - -.. lingui-cli-option:: --watch - -Watch mode. - -Watches only for changes in files in paths defined in config file or in the command itself. - -Remember to use this only in development as this command do not cleans obsolete translations. - -.. lingui-cli-option:: --debounce - -Debounce, when used with ``--debounce ``, delays extraction for ```` milliseconds, -bundling multiple file changes together. - -``extract-template`` --------------------- - -.. lingui-cli:: extract-template [--verbose] - -This command extracts messages from source files and creates a ``.pot`` template file. - -.. lingui-cli-option:: --verbose - -Prints additional information. - -``compile`` ------------ - -.. lingui-cli:: compile [--strict] [--format ] [--verbose] [--namespace ] [--watch [--debounce ]] - -This command compiles message catalogs in :conf:`localeDir` and outputs -minified Javascript files. Each message is replaced with a function -that returns the translated message when called. - -Also, language data (pluralizations) are written to the message catalog as well. - -.. lingui-cli-option:: --strict - -Fail if a catalog has missing translations. - -.. lingui-cli-option:: --format - -Format of message catalogs (see :conf:`format` option). - -.. lingui-cli-option:: --verbose - -Prints additional information. - -.. lingui-cli-option:: --namespace - -Specify namespace for compiled message catalogs (also see :conf:`compileNamespace` for -global configuration). - -.. lingui-cli-option:: --typescript - -Is the same as using :conf:`compileNamespace` with the value "ts". -Generates a {compiledFile}.d.ts and the compiled file is generated using the extension .ts - -.. lingui-cli-option:: --watch - -Watch mode. - -Watches only for changes in locale files in your defined locale catalogs. For ex. ``locales\en\messages.po`` - -.. lingui-cli-option:: --debounce - -Debounce, when used with ``--debounce ``, delays compilation for ```` milliseconds, -to avoid compiling multiple times for subsequent file changes. diff --git a/docs/ref/conf.rst b/docs/ref/conf.rst deleted file mode 100644 index cb0c50ace..000000000 --- a/docs/ref/conf.rst +++ /dev/null @@ -1,533 +0,0 @@ -******************** -Lingui Configuration -******************** - -Configuration is read from 3 different sources (the first found wins): - -- from ``lingui`` section in ``package.json`` -- from ``.linguirc`` -- from ``lingui.config.js`` -- from ``lingui.config.ts`` _(since 3.4.0)_ - -You can also define environment variable ``LINGUI_CONFIG`` with path to your config file. - -In the case of TypeScript based config you can use ESM format and `export default`. - -Default config: - -.. code-block:: json - - { - "catalogs": [{ - "path": "/locale/{locale}/messages", - "include": [""], - "exclude": ["**/node_modules/**"] - }], - "compileNamespace": "cjs", - "extractBabelOptions": {}, - "compilerBabelOptions": {}, - "fallbackLocales": {}, - "format": "po", - "locales": [], - "extractors": ["babel"], - "orderBy": "messageId", - "pseudoLocale": "", - "rootDir": ".", - "runtimeConfigModule": ["@lingui/core", "i18n"], - "sourceLocale": "", - } - -.. contents:: - :local: - :depth: 1 - -.. config:: catalogs - -catalogs --------- - -Default: - -.. code-block:: js - - [{ - path: "/locale/{locale}/messages", - include: [""], - exclude: ["**/node_modules/**"] - }] - -Defines location of message catalogs and what files are included when -:cli:`extract` is scanning for messages. - -``path`` shouldn't end with slash and it shouldn't include file extension which -depends on :conf:`format`. ``{locale}`` token is replaced by catalog locale. - -Patterns in ``include`` and ``exclude`` are passed to `minimatch `_. - -``path``, ``include`` and ``exclude`` patterns might include ```` token, which -is replaced by value of :conf:`rootDir`. - -``{name}`` token in ``path`` is replaced with a catalog name. Source path must -include ``{name}`` pattern as well and it works as a ``*`` glob pattern: - -.. code-block:: js - - { - catalogs: [{ - path: "./components/{name}/locale/{locale}", - include: ["./components/{name}/"], - }] - } - -Examples -^^^^^^^^ - -Let's assume we use ``locales: ["en", "cs"]`` and ``format: "po"`` in all examples. - -All catalogs in one directory -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: js - - { - catalogs: [{ - path: "locales/{locale}", - }] - } - -.. code-block:: - - locales/ - ├── en.po - └── cs.po - -Catalogs in separate directories -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: js - - { - catalogs: [{ - path: "locales/{locale}/messages", - }] - } - -.. code-block:: - - locales - ├── en/ - │ └── messages.po - └── cs/ - └── messages.po - -Separate catalogs per component, placed inside component dir -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: js - - { - catalogs: [{ - path: "components/{name}/locale/{locale}", - include: "components/{name}/" - }] - } - -.. code-block:: - - components/ - ├── RegistrationForm/ - │ ├── locale/ - │ │ ├── en.po - │ │ └── cs.po - │ ├── RegistrationForm.test.js - │ └── RegistrationForm.js - └── LoginForm/ - ├── locale/ - │ ├── en.po - │ └── cs.po - ├── LoginForm.test.js - └── LoginForm.js - -Separate catalogs per component, placed inside shared directory -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: js - - { - catalogs: [{ - path: "locale/{locale}/{name}", - include: "components/{name}/" - }] - } - -.. code-block:: - - . - ├── locale/ - │ ├── en/ - │ │ ├── RegistrationForm.po - │ │ └── LoginForm.po - │ └── cs/ - │ ├── RegistrationForm.po - │ └── LoginForm.po - └── components/ - ├── RegistrationForm/ - │ ├── RegistrationForm.test.js - │ └── RegistrationForm.js - └── LoginForm/ - ├── LoginForm.test.js - └── LoginForm.js - -.. config:: compileNamespace - -compileNamespace ----------------- - -Default: ``cjs`` - -Specify namespace for exporting compiled messages. See :cli:`compile` command. - -cjs -^^^ - -Use CommonJS exports: - -.. code-block:: js - - /* eslint-disable */module.exports={messages: {"..."}} - -es -^^ - -Use ES6 named export: - -.. code-block:: js - - /* eslint-disable */export const messages = {"..."} - -ts -^^ - -Use ES6 named export + .ts file with an additional {compiledFile}.d.ts file: - -.. code-block:: js - - /* eslint-disable */export const messages = {"..."} - -.. code-block:: js - - import { Messages } from '@lingui/core'; - declare const messages: Messages; - export { messages }; - -(window|global)\.(.*) -^^^^^^^^^^^^^^^^^^^^^ - -Assign compiled messages to ``window`` or ``global`` object. Specify an identifier after -``window`` or ``global`` to which the catalog is assigned, e.g. ``window.i18n``. - -For example, setting :conf:`compileNamespace` to ``window.i18n`` creates file -similar to this: - -.. code-block:: js - - /* eslint-disable */window.i18n={messages: {"..."}} - -.. config:: extractBabelOptions - -extractBabelOptions -------------------- - -Default: ``{}`` - -Specify extra babel options used to parse source files when messages are being -extracted. This is required when project doesn't use standard Babel config -(e.g. Create React App). - -.. code-block:: json - - { - "extractBabelOptions": { - "plugins": ["@babel/plugin-syntax-dynamic-import"] - } - } - -.. config:: compilerBabelOptions - -compilerBabelOptions --------------------- - -Default: - -.. code-block:: json - - { - "minified": true, - "jsescOption": { - "minimal": true - } - } - - -Specify extra babel options used to generate files when messages are being -compiled. We use internaly ``@babel/generator`` that accepts some configuration for generating code with/out ASCII characters. -These are all the options available: https://github.com/mathiasbynens/jsesc - -.. code-block:: json - - { - "compilerBabelOptions": { - "jsescOption": { - "minimal": false - } - } - } - -This example configuration will compile with scaped ASCII characters. https://github.com/mathiasbynens/jsesc#minimal - -.. config:: fallbackLocales - - -fallbackLocales ---------------- - -Default: ``{}`` - -:conf:`fallbackLocales` by default is using `CLDR Parent Locales `_, unless you disable it with a `false`: - -.. code-block:: json - - { - "fallbackLocales": false - } - -:conf:`fallbackLocales` object let's us configure fallback locales to each locale instance. - -.. code-block:: json - - { - "fallbackLocales": { - "en-US": ["en-GB", "en"], - "es-MX": "es" - } - } - -On this example if any translation isn't found on `en-US` then will search on `en-GB`, after that if not found we'll search in `en` - -Also, we can configure a default one for everything: - -.. code-block:: json - - { - "fallbackLocales": { - "en-US": ["en-GB", "en"], - "es-MX": "es", - "default": "en" - } - } - -Translations from :conf:`fallbackLocales` is used when translation for given locale is missing. - -If :conf:`fallbackLocales` is `false` default message or message ID is used instead. - -.. config:: format - -format ------- - -Default: ``po`` - -Format of message catalogs. Possible values are: - -po -^^ - -Gettext PO file: - -.. code-block:: po - - #, Comment for translators - #: src/App.js:4, src/Component.js:2 - msgid "MessageID" - msgstr "Translated Message" - -po-gettext -^^^^^^^^^^ - -Uses PO files but with gettext-style plurals, see :ref:`po-gettext`. - -minimal -^^^^^^^ - -Simple JSON with message ID -> translation mapping. All metadata (default -message, comments for translators, message origin, etc) are stripped: - -.. code-block:: json - - { - "MessageID": "Translated Message" - } - -lingui -^^^^^^ - -Raw catalog data serialized to JSON: - -.. code-block:: json - - { - "MessageID": { - "translation": "Translated Message", - "defaults": "Default string (from source code)", - "origin": [ - ["path/to/src.js", 42] - ] - } - } - -Origin is filename and line number from where the message was extracted. - -Note that origins may produce a large amount of merge conflicts. Origins can be -disabled by setting ``origins: false`` in :conf:`formatOptions`. - -Also, you can disable just ``lineNumbers`` but keep ``origins`` - -.. config:: formatOptions - -formatOptions -------------- - -Default: ``{ origins: true, lineNumbers: true }`` - -Object for configuring message catalog output. See individual formats for options. - -.. config:: locales - -locales -------- - -Default: ``[]`` - -Locale tags which are used in the project. :cli:`extract` and :cli:`compile` -writes one catalog for each locale. Each locale should be a valid `BCP-47 code `_ code. If you use a string that is not a BCP-47, make sure to use a BCP-47 when defining plurals in 18n.loadLocaleData. - -For example for `pt-br`: ``i18n.loadLocaleData('pt-br', { plurals: pt })`` - - -orderBy -------- - -Default: ``messageId`` - -Order of messages in catalog: - -messageId -^^^^^^^^^ - -Sort by the message ID. - -origin -^^^^^^^ - -Sort by message origin (e.g. ``App.js:3``) - -pseudoLocale ------------- - -Default: ``""`` - -Locale used for pseudolocalization. For example when you set ``pseudoLocale: "en"`` -then all messages in ``en`` catalog will be pseudo localized. The locale has to be included -in :conf:`locales` config. - -rootDir -------- - -Default: The root of the directory containing your Lingui config file or the ``package.json``. - -The root directory that Lingui CLI should scan when extracting messages from -source files. - -Note that using ```` as a string token in any other path-based config -settings will refer back to this value. - -.. config:: runtimeConfigModule - -runtimeConfigModule -------------------- - -Default: ``["@lingui/core", "i18n"]`` - -Module path with exported i18n object. The first value in array is module path, -the second is the import identifier. This value is used in macros, which need -to reference the global ``i18n`` object. - -You only need to set this alue if you use custom object created using :js:func:`setupI18n`: - -.. code-block:: jsx - - // If you import `i18n` object from custom module like this: - import { i18n } from "./custom-i18n-config" - - // ... then add following line to Lingui configuration: - // "runtimeConfigModule": ["./custom-i18n-config", "i18n"] - -You may use a different named export: - -.. code-block:: jsx - - import { myI18n } from "./custom-i18n-config" - // "runtimeConfigModule": ["./custom-i18n-config", "myI18n"] - -.. config:: sourceLocale - -In some advanced cases you may also need to change the module from which -`Trans` is imported. To do that, pass an object to `runtimeConfigModule`: - -.. code-block:: jsx - - // If you import `i18n` object from custom module like this: - import { Trans, i18n } from "./custom-config" - - // ... then add following line to Lingui configuration: - // "runtimeConfigModule": { - // i18n: ["./custom-config", "i18n"], - // Trans: ["./custom-config", "Trans"] - // } - -sourceLocale ------------- - -Default: ``''`` - -Locale of message IDs, which is used in source files. -Catalog for :conf:`sourceLocale` doesn't require translated messages, because message -IDs are used by default. However, it's still possible to override message ID by -providing custom translation. - -The difference between :conf:`fallbackLocales` and :conf:`sourceLocale` is that -:conf:`fallbackLocales` is used in translation, while :conf:`sourceLocale` is -used for the message ID. - -extractors ------------- - -Default: ``[babel]`` - -Extractors it's the way to customize which extractor you want for your codebase, a long time ago Babel wasn't ready yet to work with Typescript, -so we added two extractors as default ``[babel, typescript]``, but right now Babel already works good with Typescript so isn't a requirement anymore to compile two times the same code. - -Anyway, if you want to use the typescript extractor in conjuntion with babel you can do: - -.. code-block:: js - - { - "extractors": [ - require.resolve("@lingui/cli/api/extractors/babel"), - require.resolve("@lingui/cli/api/extractors/typescript"), - ] - } - -Of course you can build your own extractor, take a look to babel and typescript extractors to see how you should do it, but basically exports two methods: - - match: regex to a filename extension, should return true|false - - extract: is the responsible of transforming the code and using @lingui/babel-plugin-extract-messages diff --git a/docs/ref/core.rst b/docs/ref/core.rst deleted file mode 100644 index 24752bde8..000000000 --- a/docs/ref/core.rst +++ /dev/null @@ -1,404 +0,0 @@ -.. _ref-core: - -****************************************** -@lingui/core - The core i18n functionality -****************************************** - -``@lingui/core`` package provides the main i18n object which manages message catalogs, -active locale as well as translation and formatting of messages. - -Installation -============ - -.. code-block:: sh - - npm install --save @lingui/core - - # Or using yarn - # yarn add @lingui/core - -Overview -======== - -``@lingui/core`` package exports the global instance of ``i18n`` object. Simply import -it and use it: - -.. code-block:: jsx - - import { i18n } from "@lingui/core" - - // import plural rules for all locales - import { en, cs } from "make-plural/plurals" - - i18n.loadLocaleData("en", { plurals: en }) - i18n.loadLocaleData("cs", { plurals: cs }) - - /** - * Load messages for requested locale and activate it. - * This function isn't part of the LinguiJS library because there are - * many ways how to load messages — from REST API, from file, from cache, etc. - */ - async function activate(locale: string) { - const { messages } = await import(`${locale}/messages.js`) - i18n.load(locale, messages) - i18n.activate(locale) - } - - activate("cs") - - // returns the Czech translation of "Hello World" - const translation = i18n._("Hello World") - -If you don't want to use the global ``i18n`` instance and you want to setup your own, -you can use :js:func:`setupI18n` method. You also need to set :conf:`runtimeConfigModule` -for macros to work correctly: - -.. code-block:: js - - // If you import `i18n` object from custom module like this: - import { i18n } from "./custom-i18n-config" - - // ... then add following line to your Lingui configuration: - // "runtimeConfigModule": ["./custom-i18n-config", "i18n"] - -Reference -========= - -.. js:class:: I18n - - .. js:method:: load(catalogs: Catalogs) - .. js:method:: load(locale: string, catalog: Catalog) - - Load catalog for given locale or load multiple catalogs at once. - - .. code-block:: js - - import { i18n } from "@lingui/core" - - const messages = { - "Hello": "Hello", - "Good bye": "Good bye", - - // Just an example how catalog looks internally. - // Formatting of string messages works in development only. - // See note below. - "My name is {name}": "My name is {name}" - } - - const messagesCs = { - "Hello": "Ahoj", - "Good bye": "Nashledanou", - "My name is {name}": "Jmenuji se {name}" - } - - i18n.load({ - en: messagesEn, - cs: messagesCs - }) - - // This is the same as loading message catalogs separately per language: - // i18n.load('en', messagesEn) - // i18n.load('cs', messagesCs) - - .. important:: Don't write catalogs manually - - Code above contains an example of message catalogs. In real applications, - messages are loaded from external message catalogs generated by :cli:`compile` - command. - - Formatting of messages as strings (e.g: ``"My name is {name}"``) works in - development only, when messages are parsed on the fly. In production, however, - messages must be compiled using :cli:`compile` command. - - The same example would in real application look like this: - - .. code-block:: js - - import { i18n } from "@lingui/core" - - // File generated by `lingui compile` - import { messages: messagesEn } from "./locale/en/messages.js" - - i18n.load('en', messagesEn) - - .. js:method:: activate(locale [, locales]) - - Activate a locale and locales. :js:meth:`_` from now on will return messages - in given locale. - - .. code-block:: js - - import { i18n } from "@lingui/core" - - i18n.activate("en") - i18n._("Hello") // Return "Hello" in English - - i18n.activate("cs") - i18n._("Hello") // Return "Hello" in Czech - - .. js:method:: _(messageId [, values [, options]]) - - The core method for translating and formatting messages. - - ``messageId`` is a unique message ID which identifies message in catalog. - - ``values`` is an object of variables used in translated message. - - ``options.defaults`` is the default translation (optional). This is mostly used when - application doesn't use message IDs in natural language (e.g.: ``msg.id`` or - ``Component.title``). - - .. code-block:: js - - import { i18n } from "@lingui/core" - - // Simple message - i18n._("Hello") - - // Message with variables - i18n._("My name is {name}", { name: "Tom" }) - - // Message with custom messageId - i18n._("msg.id", { name: "Tom" }, { defaults: "My name is {name}" }) - - .. js:method:: date(value: string | Date[, format: Intl.DateTimeFormatOptions]) - - :returns: Formatted date string - - Format a date using the conventional format for the active language. - - ``date`` is a Date object to be formatted. When ``date`` is a string, the Date object is created by ``new Date(date)``. - - ``format`` is an object passed to the ``options`` argument of the `Intl.DateTimeFormat constructor `_ (optional). - - .. code-block:: js - - import { i18n } from "@lingui/core" - - const d = new Date("2021-07-23T16:23:00") - - i18n.activate("en") - i18n.date(d) - // Returns "7/23/2021" - - i18n.date(d, { timeStyle: "medium"}) - // Returns "4:23:00 PM" - - i18n.date(d, { dateStyle: "medium", timeStyle: "medium"}) - // Returns "Jul 23, 2021, 4:23:00 PM" - - i18n.activate("cs") - i18n.date(d) - // Returns "23. 7. 2021" - - - .. js:method:: number(value: number[, format: Intl.NumberFormatOptions]) - - :returns: Formatted number string - - Format a number using the conventional format for the active language. - - ``number`` is a number to be formatted. - - ``format`` is an object passed to the ``options`` argument of the `Intl.NumberFormat constructor `_ (optional). - - .. code-block:: js - - import { i18n } from "@lingui/core" - - i18n.activate("en") - i18n.number(12345.678) - // Returns "12,345.678" - - i18n.number(12345.678, { style: "currency", currency: "USD"}) - // Returns "$12,345.68" - - i18n.activate("cs") - i18n.number(12345.678) - // Returns "12 345,678" - - i18n.number(12345.678, { style: "currency", currency: "CZK"}) - // Returns "12 345,68 Kč" - -.. js:function:: setupI18n([options]) - - :returns: instance of I18n - - Initialize and return a new I18n instance. Usually you want to call it just once - and then use returned ``i18n`` object across whole codebase. - - .. important:: You don't need to setup i18n instance - - In most cases you can use the global ``i18n`` object exported from ``@lingui/core`` - directly. - - However, if you do need to setup your own ``i18n`` instance, remember to also - set :conf:`runtimeConfigModule` work macros to work properly: - - .. code-block:: js - - // If you import `i18n` object from custom module like this: - import { i18n } from "./custom-i18n-config" - - // ... then add following line to your Lingui configuration: - // "runtimeConfigModule": ["./custom-i18n-config", "i18n"] - - .. code-block:: js - - import { setupI18n } from "@lingui/core" - - const i18n = setupI18n() - - The factory function accepts one optional parameter, ``options``: - - .. js:attribute:: options.locale - - Initial active locale. - - .. code-block:: jsx - - import { setupI18n } from "@lingui/core" - - const i18n = setupI18n({ locale: "en" }) - - // This is a shortcut for: - // const i18n = setupI18n() - // i18n.activate("en") - - .. js:attribute:: options.locales - - List of alternative locales (BCP 47 langauge tags) which are used for number and date - formatting (some countries use more than one number/date format). If not set, active - locale is used instead. - - .. code-block:: jsx - - import { setupI18n } from "@lingui/core" - - const i18n = setupI18n({ - locale: "ar", - locales: ["en-UK", "ar-AS"] - }) - - // This is a shortcut for: - // const i18n = setupI18n() - // i18n.activate("en", ["en-UK", "ar-AS"]) - - .. js:attribute:: options.messages - - Initial :js:data:`Messages`. - - .. code-block:: jsx - - import { setupI18n } from "@lingui/core" - - const messages: { - en: require("./locale/en/messages").messages, // your path to compiled messages here - cs: require("./locale/cs/messages").messages // your path to compiled messages here - } - const i18n = setupI18n({ messages }) - - // This is a shortcut for: - // const i18n = setupI18n() - // i18n.load(messages) - - .. js:attribute:: options.missing - - Custom message to be returned when translation is missing. This is useful for - debugging: - - .. code-block:: jsx - - import { setupI18n } from "@lingui/core" - - const i18n = setupI18n({ missing: "🚨" }) - i18n._('missing translation') === "🚨" - - This might be also a function which is called with active language and message ID: - - .. code-block:: jsx - - import { setupI18n } from "@lingui/core" - - function missing(language, id) { - alert(`Translation in ${language} for ${id} is missing!`) - return id - } - - const i18n = setupI18n({ missing }) - i18n._('missing translation') // raises alert - - - -.. js:data:: Catalogs - - Type of ``catalogs`` parameters in :js:meth:`I18n.load` method: - - .. code-block:: js - - type Catalogs = {[locale: string]: Catalog} - - // Example: - const catalogs: Catalogs = { - en: { - messages: { - "Hello": "Hello", - "Good bye": "Good bye" - } - }, - cs: { - messages: { - "Hello": "Ahoj", - "Good bye": "Nashledanou" - } - } - } - -.. js:data:: Catalog - - Message catalog contains messages and language data (plurals). This object is - usually generated in CLI: - - .. code-block:: js - - type Catalog = { - languageData: { - plurals: Function - }, - messages: Messages - } - -.. js:data:: Messages - - Type of messages in :js:data:`Catalogs`. It's a mapping of a **messageId** to a - translation in given language. This may be a function if messages are compiled. - - .. code-block:: js - - type Messages = {[messageId: string]: string | Function} - - // Example - const messagesEn: Messages = { - "Hello": "Hello", - "Good bye": "Good bye" - } - -Events -====== - -change ------- - -Triggered **after** locale is changed or new catalog is loaded. There are no arguments. - -missing -------- - -Triggered when a translation is requested with ``i18n._`` that does not exist in the active locale's messages. -Information about the locale and message are available from the event. - -.. code-block:: js - - i18n.on('missing', (event) => { - alert(`alert(`Translation in ${event.locale} for ${event.id} is missing!`)`) - }) diff --git a/docs/ref/loader.rst b/docs/ref/loader.rst deleted file mode 100644 index 1f2994cf6..000000000 --- a/docs/ref/loader.rst +++ /dev/null @@ -1,39 +0,0 @@ -*********************************************** -API Reference - Webpack Loader (@lingui/loader) -*********************************************** - -It's a good practice to use compiled message catalogs during development. However, -running :cli:`compile` everytime messages are changed soon becomes tedious. - -``@lingui/loader`` is a webpack loader, which compiles messages on the fly: - -Installation -============ - -Install ``@lingui/loader`` as a development dependency: - -.. code-block:: sh - - npm install --save-dev @lingui/loader - - # Or using yarn - # yarn add --dev @lingui/loader - -Usage -===== - -Simply prepend ``@lingui/loader:`` in front of path to message catalog you want to -import. Here's an example of dynamic import: - -Extension is mandatory. If you use minimal or lingui file format, use ``.json``. In case of using po format, use ``.po``. - -.. code-block:: jsx - - export async function dynamicActivate(locale: string) { - const { messages } = await import(`@lingui/loader!./locales/${locale}/messages.json`) - i18n.load(locale, messages) - i18n.activate(locale) - } - -See the `guide about dynamic loading catalogs <../guides/dynamic-loading-catalogs.html>`_ -for more info. diff --git a/docs/ref/locale-detector.rst b/docs/ref/locale-detector.rst deleted file mode 100644 index a5fdcd3f0..000000000 --- a/docs/ref/locale-detector.rst +++ /dev/null @@ -1,74 +0,0 @@ -******************************************************** -API Reference - Locale Detection (@lingui/detect-locale) -******************************************************** - -``@lingui/detect-locale`` is little package ``just (922 B Gzip)`` with some helper functions that will help you detect the locale of the user: - -Installation -============ - -Install ``@lingui/detect-locale`` as a dependency: - -.. code-block:: sh - - npm install --save @lingui/detect-locale - - # Or using yarn - # yarn add @lingui/detect-locale - -Usage -===== - -``@lingui/detect-locale:`` exports multiple methods: - - ``detect`` - `Will return the first occurence of detectors` - - ``multipleDetect`` - `Will return an array with all the locales detected by each detector` - -and some helpers: - - ``fromCookie(key: string)`` - `Accepts a key as param will recover from navigator cookies the value` - - ``fromHtmlTag(tag: string)`` - `Will find on HtmlDocument the attribute passed in params (normally it's used lang or xml:lang)` - - ``fromNavigator()`` - `Recovers the navigator language, it's also compatible with old browsers like IE11` - - ``fromPath(localePathIndex: number)`` - `Splits the location.pathname in an array so you have to specify the index of the array where's locale is set` - - ``fromStorage(key: string, { useSessionStorage: boolean }`` - `Will search on localStorage by default the item that has that key, if **useSessionStorage** is passed, will search on sessionStorage` - - ``fromSubdomain(localeSubdomainIndex: number)`` - `Like fromPath, splits the location.href on segments you must specify the index of that segment` - - ``fromUrl(parameter: string)`` - `Uses a query-string parser to recover the correct parameter` - -Practically all detectors accepts a custom document, location, or window object as param, it's usefull when testing or using some server-side strategy - -Usage with ``detect`` -===================== - -.. code-block:: jsx - - import { detect, fromUrl, fromStorage, fromNavigator } from "@lingui/detect-locale" - - // can be a function with custom logic or just a string, `detect` method will handle it - const DEFAULT_FALLBACK = () => "en" - - const result = detect( - fromUrl("lang"), - fromStorage("lang"), - fromNavigator(), - DEFAULT_FALLBACK - ) - - console.log(result) // "en" - - -Usage with ``multipleDetect`` -============================= - -.. code-block:: jsx - - import { multipleDetect, fromUrl, fromStorage, fromNavigator } from "@lingui/detect-locale" - - // can be a function with custom logic or just a string, `detect` method will handle it - const DEFAULT_FALLBACK = () => "en" - - const result = multipleDetect( - fromUrl("lang"), - fromStorage("lang"), - fromNavigator(), - DEFAULT_FALLBACK - ) - - console.log(result) // ["en", "es"] diff --git a/docs/ref/macro.rst b/docs/ref/macro.rst deleted file mode 100644 index 509e27927..000000000 --- a/docs/ref/macro.rst +++ /dev/null @@ -1,840 +0,0 @@ -************************* -@lingui/macro - Reference -************************* - -``@lingui/macro`` package provides `babel macros `_ which -transforms JavaScript objects and JSX elements into messages in ICU MessageFormat. - -Installation -============ - -Babel macros require babel-plugin-macros_ to work. If you use a framework -(for example GatsbyJS, Create React App >2.0) you might already have macros enabled. -Otherwise install it as any other Babel plugin: - -1. Install ``babel-plugin-macros`` as a dev dependency and ``@lingui/macro`` as dependency:: - - npm install --save-dev babel-plugin-macros - npm install --save @lingui/macro - - # Or using yarn - # yarn add --dev babel-plugin-macros - # yarn add @lingui/macro - - .. note:: - - It's recommended to install ``@lingui/macro`` package as a production dependency rather than development one - to avoid ``import/no-extraneous-dependencies`` errors in ESLint. - -2. Add ``macros`` to the top of plugins section in your Babel config: - - .. code-block:: json - - { - "plugins": [ - "macros" - ] - } - -Overview -======== - -The advantages of using macros are: - - - You don't need to learn ICU MessageFormat syntax. You always use familiar JS and JSx code. - - Components and functions are type checked - - Additional validation of plural rules is performed during transformation - - Non essentials data are removed from production build (e.g. comments and default messages) to shave few bytes - -**JSX macros** are transformed to :component:`Trans` component from -:doc:`@lingui/react `: - -.. code-block:: jsx - - import { Trans } from "@lingui/macro" - Attachment {name} saved - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { Trans } from "@lingui/react" - - -**JS macros** (i.e. macros that looks like a simple JavaScript functions) are -transformed into ``i18n._`` call. - -.. code-block:: jsx - - import { t } from "@lingui/macro" - t`Attachment ${name} saved` - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { i18n } from "@lingui/core" - /*i18n*/ - i18n._("Attachment {name} saved", { name }) - -.. note:: - - By default, the ``i18n`` object is imported from ``@lingui/core``. - If you use a custom instance of ``i18n`` object, you need to set - :conf:`runtimeConfigModule` or pass a custom instance to :jsmacro:`t`. - -The only exception is :jsmacro:`defineMessage` which is transformed into -message descriptor. In other words, the message isn't translated directly -and can be used anytime later: - -.. code-block:: jsx - - import { i18n } from "@lingui/core" - import { defineMessage } from "@lingui/macro" - - // define message - const message = defineMessage({ message: `Attachment ${name} saved` }) - - // translate it - i18n._(message) - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { i18n } from "@lingui/core" - - // define message - const message = /*i18n*/{ id: "Attachment {name} saved", values: { name }}) - - // translate it - i18n._(message) - -Examples of JS macros ---------------------- - -+-------------------------------------------------------------+--------------------------------------------------------------------+ -| JS Macro | Result | -+=============================================================+====================================================================+ -| .. code-block:: js | .. code-block:: js | -| | | -| t`Refresh inbox` | /*i18n*/ | -| | i18n._("Refresh inbox") | -+-------------------------------------------------------------+--------------------------------------------------------------------+ -| .. code-block:: js | .. code-block:: js | -| | | -| t`Attachment ${name} saved` | /*i18n*/ | -| | i18n._("Attachment {name} saved", { name }) | -+-------------------------------------------------------------+--------------------------------------------------------------------+ -| .. code-block:: js | .. code-block:: js | -| | | -| t(customI18n)`Refresh inbox` | /*i18n*/ | -| | customI18n._("Refresh inbox") | -+-------------------------------------------------------------+--------------------------------------------------------------------+ -| .. code-block:: js | .. code-block:: js | -| | | -| t(customI18n)`Attachment ${name} saved` | /*i18n*/ | -| | customI18n._("Attachment {name} saved", { name }) | -+-------------------------------------------------------------+--------------------------------------------------------------------+ -| .. code-block:: js | .. code-block:: js | -| | | -| plural(count, { | /*i18n*/ | -| one: "Message", | i18n._("{count, plural, one {Message} other {Messages}}", { | -| other: "Messages" | count | -| }) | }) | -+-------------------------------------------------------------+--------------------------------------------------------------------+ -| .. code-block:: js | .. code-block:: js | -| | | -| t({ | i18n._(/*i18n*/{ | -| id: "msg.refresh", | id: "msg.refresh", | -| message: "Refresh inbox" | message: "Refresh inbox" | -| }) | }) | -+-------------------------------------------------------------+--------------------------------------------------------------------+ -| .. code-block:: js | .. code-block:: js | -| | | -| t(customI18n)({ | customI18n._(/*i18n*/{ | -| id: "msg.refresh", | id: "msg.refresh", | -| message: "Refresh inbox" | message: "Refresh inbox" | -| }) | }) | -+-------------------------------------------------------------+--------------------------------------------------------------------+ -| .. code-block:: js | .. code-block:: js | -| | | -| defineMessage({ | /*i18n*/{ | -| id: "msg.refresh", | id: "msg.refresh", | -| message: "Refresh inbox" | message: "Refresh inbox" | -| }) | } | -+-------------------------------------------------------------+--------------------------------------------------------------------+ - -Examples of JSX macros ----------------------- - -+-------------------------------------------------------------+--------------------------------------------------------------------+ -| JSX Macro | Result | -+=============================================================+====================================================================+ -| .. code-block:: jsx | .. code-block:: jsx | -| | | -| Attachment {name} saved | | -+-------------------------------------------------------------+--------------------------------------------------------------------+ -| .. code-block:: jsx | .. code-block:: jsx | -| | | -| | -| /> | | -+-------------------------------------------------------------+--------------------------------------------------------------------+ -| .. code-block:: jsx | .. code-block:: jsx | -| | | -| | | message="Refresh inbox" | -| | /> | -+-------------------------------------------------------------+--------------------------------------------------------------------+ - -Usage -===== - -JS macros ---------- - -These macros can be used in any context (e.g. outside JSX). All JS macros are transformed -into a *Message Descriptor* wrapped inside of ``i18n._`` call. - -.. note:: - - By default, the ``i18n`` object is imported from ``@lingui/core``. - If you use a custom instance of ``i18n`` object, you need to set - :conf:`runtimeConfigModule` or pass a custom instance to :jsmacro:`t`. - -*Message Descriptor* is an object with message ID, default message and other parameters. -``i18n._`` accepts message descriptors and performs translation and formatting: - -.. code-block:: jsx - - type MessageDescriptor = { - id: String, - message?: String, - values?: Object, - formats?: Object, - comment?: string - } - -``id`` is message ID and the only required parameter. ``id`` and ``message`` -are extracted to message catalog. Only ``id``, ``values``, and ``formats`` -are used at runtime, all other attributes are removed from production code -for size optimization. - -.. note:: i18n comment - - In the examples below you might notice ``/*i18n*/`` comment in - macro output. This comment tells the extract plugin that following - object or string should be collected to message catalog. - -t -^ - -.. jsmacro:: t - -The most common macro for messages. It transforms tagged template literal into message -in ICU MessageFormat: - -.. code-block:: jsx - - import { t } from "@lingui/macro" - const message = t`Hello World` - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { i18n } from "@lingui/core" - const message = - /*i18n*/ - i18n._("Hello World") - -Message variables are supported: - -.. code-block:: jsx - - import { t } from "@lingui/macro" - const message = t`My name is ${name}` - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { i18n } from "@lingui/core" - const message = - /*i18n*/ - i18n._("My name is {name}", { - name - }) - -In fact, any expression can be used inside template literal. However, only -simple variables are referenced by name in a transformed message. All -other expressions are referenced by numeric index: - -.. code-block:: jsx - - import { t } from "@lingui/macro" - const message = t`Today is ${new Date()}` - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { i18n } from "@lingui/core"; - - const message = - /*i18n*/ - i18n._("Today is {0}", { - 0: new Date() - }); - -Optionally, a custom ``i18n`` instance can be passed that can be used -instead of the global instance: - -.. code-block:: jsx - - import { t } from "@lingui/macro" - import { i18n } from "./lingui" - const message = t(i18n)`Hello World` - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { i18n } from "./lingui" - const message = - /*i18n*/ - i18n._("Hello World") - -It's also possible to pass custom ``id`` and ``comment`` for translators by -calling ``t`` macro with a message descriptor: - -.. code-block:: jsx - - import { t } from "@lingui/macro" - const message = t({ - id: 'msg.hello', - comment: 'Greetings at the homepage', - message: `Hello ${name}` - }) - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { i18n } from "@lingui/core" - const message = i18n._(/*i18n*/{ - id: 'msg.hello', - comment: 'Greetings at the homepage', - message: 'Hello {name}', - values: { name } - }) - -In this case the ``message`` is used as a default message and it's transformed -as if it were wrapped in ``t`` macro. ``message`` also accepts any other macros: - -.. code-block:: jsx - - import { t } from "@lingui/macro" - const message = t({ - id: 'msg.plural', - message: plural(value, { one: "...", other: "..." }) - }) - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { i18n } from "@lingui/core" - const message = i18n._(/*i18n*/{ - id: 'msg.plural', - message: '{value, plural, one {...} other {...}}', - values: { value } - }) - -plural -^^^^^^ - -.. jsmacro:: plural - -.. code-block:: jsx - - plural(value: string | number, options: Object) - -``plural`` macro is used for pluralization, e.g: messages which has different form -based on counter. The first argument ``value`` determines the plural form. -The second argument is an object with available plural forms. Plural form -used in the source code depends on your source locale (e.g. English has only -``one`` and ``other``). - -.. code-block:: jsx - - import { plural } from "@lingui/macro" - const message = plural(count, { - one: "# Book", - other: "# Books" - }) - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { i18n } from "@lingui/core" - const message = - /*i18n*/ - i18n._('{count, plural, one {# Book} other {# Books}}', { - count - }) - -If you need to add variables to plural form, you can use template string literals. -This time :jsmacro:`t` macro isn't required as template strings -are transformed automatically: - -.. code-block:: jsx - - import { plural } from "@lingui/macro" - const message = plural(count, { - one: `${name} has # friend`, - other: `${name} has # friends` - }) - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { i18n } from "@lingui/core" - const message = - /*i18n*/ - i18n._('{count, plural, one {{name} has # friend} other {{name} has # friends}}', { - count, name - }) - -Plurals can also be nested to form complex messages. Here's an example using -two counters: - -.. code-block:: jsx - - import { plural } from "@lingui/macro" - const message = plural(numBooks, { - one: plural(numArticles, { - one: `1 book and 1 article`, - other: `1 book and ${numArticles} articles`, - }), - other: plural(numArticles, { - one: `${numBooks} books and 1 article`, - other: `${numBooks} books and ${numArticles} articles`, - }), - }) - - // ↓ ↓ ↓ ↓ ↓ ↓ - // Generated message was wrapped for better readability - - import { i18n } from "@lingui/core" - const message = - /*i18n*/ - i18n._(`{numBooks, plural, - one {{numArticles, plural, - one {1 book and 1 article} - other {1 book and {numArticles} articles} - }} - other {{numArticles, plural, - one {{numBooks} books and 1 article} - other {{numBooks} books and {numArticles} articles} - }} - }`, - { numBooks, numArticles } - ) - -.. note:: - - This is just an example how macros can be combined to create a complex messages. - However, simple is better because in the end it's the translator who's gonna - have to translate these long and complex strings. - -.. important:: - - Use ``plural`` inside :jsmacro:`t` macro if you want to add custom ``id`` - or ``comment`` for translators. - -selectOrdinal -^^^^^^^^^^^^^ - -.. jsmacro:: selectOrdinal - -.. code-block:: jsx - - selectOrdinal(value: string | number, options: Object) - -``selectOrdinal`` macro is similar to :jsmacro:`plural` but instead of using -cardinal plural forms it uses ordinal forms: - -.. code-block:: jsx - - import { selectOrdinal } from "@lingui/macro" - const message = selectOrdinal(count, { - one: "#st", - two: "#nd", - few: "#rd", - other: "#th", - }) - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { i18n } from "@lingui/core" - const message = - /*i18n*/ - i18n._('{count, selectOrdinal, one {#st} two {#nd} few {#rd} other {#th}}', { - count - }) - -.. important:: - - Use ``selectOrdinal`` inside :jsmacro:`t` macro if you want to add custom ``id`` - or ``comment`` for translators. - -select -^^^^^^ - -.. jsmacro:: select - -.. code-block:: jsx - - select(value: string | number, options: Object) - -``select`` macro works as a switch statement — it select one of the forms -provided in ``options`` object which key matches exactly ``value``: - -.. code-block:: jsx - - import { select } from "@lingui/macro" - const message = select(gender, { - male: "he", - female: "she", - other: "they" - }) - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { i18n } from "@lingui/core" - const message = - /*i18n*/ - i18n._('{gender, select, male {he} female {she} other {they}}', { - gender - }) - -.. important:: - - Use ``select`` inside :jsmacro:`t` macro if you want to add custom ``id`` - or ``comment`` for translators. - -defineMessage -^^^^^^^^^^^^^ - -.. jsmacro:: defineMessage - -``defineMessage`` macro is a wrapper around macros above which allows you -to add comments for translators or override the message ID. - -Unlike the other JS macros, it doesn't wrap generated *MessageDescription* into -``i18n._`` call. - -.. code-block:: js - - type MessageDescriptor = { - id?: string, - message?: string, - comment?: string - } - - defineMessage(message: MessageDescriptor) - -Either ``id`` or ``message`` property is required. - -``id`` is a custom message id. If it isn't set, the ``message`` is used instead. - -.. code-block:: jsx - - import { defineMessage } from "@lingui/macro" - const message = defineMessage({ - id: "Navigation / About", - message: "About us" - }) - - // ↓ ↓ ↓ ↓ ↓ ↓ - - const message = /*i18n*/{ - id: 'Navigation / About', - message: "About us" - } - -``message`` is the default message. Any JS macro can be used here. Template -string literals don't need to be tagged with :jsmacro:`t`. - -.. code-block:: jsx - - import { defineMessage, t } from "@lingui/macro" - - const name = "Joe" - - const message = defineMessage({ - comment: "Greetings on the welcome page", - message: `Welcome, ${name}!` - }) - - // ↓ ↓ ↓ ↓ ↓ ↓ - - const message = /*i18n*/{ - comment: "Greetings on the welcome page", - message: "Welcome, {name}", - values: { - name - } - } - -``comment`` is a comment for translators. It's extracted to the message catalog -and it gives extra context for translators. It's removed from production code: - -.. code-block:: jsx - - import { defineMessage } from "@lingui/macro" - const message = defineMessage({ - comment: "Link in navigation pointing to About page", - message: "About us" - }) - - // ↓ ↓ ↓ ↓ ↓ ↓ - - const message = /*i18n*/{ - comment: "Link in navigation pointing to About page", - id: "About us" - } - -.. note:: - - In production build, the whole macro is replaced with an ``id``: - - .. code-block:: jsx - - import { defineMessage } from "@lingui/macro" - const message = defineMessage({ - id: "Navigation / About", - comment: "Link in navigation pointing to About page", - message: "About us" - }) - - // process.env.NODE_ENV === "production" - // ↓ ↓ ↓ ↓ ↓ ↓ - - const message = "Navigation / About" - - ``message`` and ``comment`` are used in message catalogs only. - -JSX Macros ----------- - -Common props -^^^^^^^^^^^^ - -All macros share following props: - -id -~~ - -Each message in catalog is identified by **message ID**. - -While all macros use generated message as the ID, it's possible to override it. -In such case, generated message is used as a default translation. - -.. code-block:: jsx - - import { Trans } from "@lingui/macro" - Attachment {name} saved. - - // ↓ ↓ ↓ ↓ ↓ ↓ - import { Trans } from "@lingui/react" - - -comment -~~~~~~~~~~~ - -Comment for translators to give them additional context about the message. -It's removed from production code. - -render -~~~~~~ - -Render prop function used to render translation. This prop is directly passed to -:component:`Trans` component from :doc:`@lingui/react `. See -`rendering of translations `_ for more info. - -Trans -^^^^^ - -.. jsxmacro:: Trans - - :prop string id: Custom message ID - :prop string comment: Comment for translators - -:jsxmacro:`Trans` is the basic macro for static messages, messages with variables, -but also for messages with inline markup: - -.. code-block:: jsx - - import { Trans } from "@lingui/macro" - Refresh inbox; - - // ↓ ↓ ↓ ↓ ↓ ↓ - import { Trans } from "@lingui/react" - - -Custom ``id`` is preserved: - -.. code-block:: jsx - - import { Trans } from "@lingui/macro" - Attachment {name} saved. - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { Trans } from "@lingui/react" - - -This macro is especially useful when message contains inline markup. - -.. code-block:: jsx - - import { Trans } from "@lingui/macro" - - Read the docs.; - - // ↓ ↓ ↓ ↓ ↓ ↓ - - import { Trans } from "@lingui/macro" - }} /> - -Components and HTML tags are replaced with dummy indexed tags (``<0>``) which -has several advatanges: - -- both custom React components and built-in HTML tags are supported -- change of component props doesn't break the translation -- the message is extracted as a whole sentence (this seems to be obvious, but most - i18n libs simply split message into pieces by tags and translate them separately) - -Plural -^^^^^^ - -.. jsxmacro:: Plural - - :prop number value: (required) Value is mapped to plural form below - :prop string|Object format: Number format passed as options to `Intl.NumberFormat`_ - :prop number offset: Offset of value when calculating plural forms - :prop string zero: Form for empty ``value`` - :prop string one: *Singular* form - :prop string two: *Dual* form - :prop string few: *Paucal* form - :prop string many: *Plural* form - :prop string other: (required) general *plural* form - :prop string _: Exact match form, corresponds to ``=N`` rule - - MessageFormat: ``{arg, plural, ...forms}`` - -Props of :jsxmacro:`Plural` macro are transformed into :icu:`plural` format. - -.. code-block:: jsx - - import { Plural } from "@lingui/macro" - - - // ↓ ↓ ↓ ↓ ↓ ↓ - import { Trans } from "@lingui/react" - - -``#`` are formatted using :icu:`number` format. ``format`` prop is passed to this -formatter. - -Exact matches in MessageFormat syntax are expressed as ``=int`` (e.g. ``=0``), -but in React this isn't a valid prop name. Therefore, exact matches are expressed as -``_int`` prop (e.g. ``_0``). This is commonly used in combination with -``offset`` prop. ``offset`` affects only plural forms, not exact matches. - -.. code-block:: jsx - - import { Plural } from "@lingui/macro" - - `one` plural form - one="You and # other guest arrived" - - // when value >= 3 - other="You and # other guests arrived" - /> - - /* - This is transformed to Trans component with ID: - {count, plural, offset:1 _0 {Nobody arrived} - _1 {Only you arrived} - one {You and # other guest arrived} - other {You and # other guests arrived}} - */ - -SelectOrdinal -^^^^^^^^^^^^^ - -.. jsxmacro:: SelectOrdinal - - :prop number value: (required) Value is mapped to plural form below - :prop number offset: Offset of value for plural forms - :prop string zero: Form for empty ``value`` - :prop string one: *Singular* form - :prop string two: *Dual* form - :prop string few: *Paucal* form - :prop string many: *Plural* form - :prop string other: (required) general *plural* form - :prop string _: Exact match form, correspond to ``=N`` rule. (e.g: ``_0``, ``_1``) - :prop string|Object format: Number format passed as options to `Intl.NumberFormat`_ - - MessageFormat: ``{arg, selectordinal, ...forms}`` - -Props of :jsxmacro:`SelectOrdinal` macro are transformed into :icu:`selectOrdinal` -format: - -.. code-block:: jsx - - import { SelectOrdinal } from "@lingui/macro" - - // count == 1 -> 1st - // count == 2 -> 2nd - // count == 3 -> 3rd - // count == 4 -> 4th - - -Select -^^^^^^ - -.. jsxmacro:: Select - - :prop number value: (required) Value determines which form is outputted - :prop number other: (required) Default, catch-all form - - MessageFormat: ``{arg, select, ...forms}`` - -Props of :jsxmacro:`Select` macro are transformed into :icu:`select` format: - -.. code-block:: jsx - - import { Select } from "@lingui/macro" - - // gender == "female" -> Her book - // gender == "male" -> His book - // gender == "unspecified" -> Their book - +``` diff --git a/website/docs/ref/message-format.md b/website/docs/ref/message-format.md new file mode 100644 index 000000000..a98e2c27f --- /dev/null +++ b/website/docs/ref/message-format.md @@ -0,0 +1,59 @@ +# ICU MessageFormat + +ICU MessageFormat is a flexible yet powerful syntax to express all nuances of grammar for each language. + +## Overview + +### Simple text + +Example: `Refresh inbox` + +### Variables + +Example: `Attachment {name} saved` + +### Plurals +> Using language specific plural forms (`one`, `other`): + +``` icu-message-format +{count, plural, one {Message} other {Messages}} +``` + +> Using exact matches (`=0`): + +``` icu-message-format +{count, plural, =0 {No messages} + one {# message} + other {# messages}} +``` + +> Offsetting plural form: + +``` icu-message-format +{count, plural, offset:1 + =0 {Nobody read this message} + =1 {Only you read this message} + one {You and # friend read this message} + other {You and # friends read this message} +``` + +### Select + +``` icu-message-format +{gender, select, male {He replied to your message} + female {She replied to your message} + other {They replied to your message}} +``` + +### Ordinals + +``` icu-message-format +{count, selectOrdinal, one {#st message} + two {#nd message} + few {#rd message} + other {#th message}} +``` + +## Further reading + +- [ICU Playground](https://format-message.github.io/icu-message-format-for-translators/editor.html) diff --git a/website/docs/ref/react.md b/website/docs/ref/react.md new file mode 100644 index 000000000..eb95966b6 --- /dev/null +++ b/website/docs/ref/react.md @@ -0,0 +1,210 @@ +# React API Reference + +Components from `@lingui/react` wrap the vanilla JS API from `lingui-i18n`. React components handle changes of active language or interpolated variables better than low-level API and also take care of re-rendering when wrapped inside pure components. + +## General Concepts + +### Rendering of Translations {#rendering-translations} + +All i18n components render translation as a text without a wrapping tag. This can be customized in three different ways: + +- globally: using `defaultComponent` prop on [`I18nProvider`](#i18nprovider) component; +- locally: using `render` prop or `component` on i18 components + +#### Global Configuration + +Default rendering component can be set using `defaultComponent` prop in [`I18nProvider`](#i18nprovider). The main use case for this is rendering translations in `` component in React Native. + +~~It's possible to pass in either a string for built-in elements (`span`, `h1`)~~, React elements or React classes. This prop has the same type as `render` and `component` prop on i18n components described below. + +#### Local Configuration + +| Prop name | Type | Description | +|-------------| ----------------------------------------- | -------------------------------------------- | +| `className` | string | Class name to be added to `` element | +| `render` | *Function(props) -> Element \| Component* | Custom wrapper rendered as function | +| `component` | Component, `null` | Custom wrapper element to render translation | + +`className` is used only for built-in components (when *render* is string). + +`Function(props)` props returns the translation, an id, and a message. + +When `component` is **React.Element** ~~or **string** (built-in tags)~~, it is rendered with the `translation` passed in as its child: + +``` jsx +import { Text } from "react-native"; + +Link to docs; +// renders as Link to docs +``` + +To get more control over the rendering of translation, use instead the `render` method with **React.Component** (or stateless component). Component passed to `render` will receive the translation value as a `translation` prop: + +``` jsx +// custom component + }> + Sign in +; +// renders as +``` + +`render` or `component` also accepts `null` value to render string without wrapping component. This can be used to override custom `defaultComponent` config. + +``` jsx +Heading; +// renders as "Heading" + +Heading; +// renders as "Heading" +``` + +## Components + +### Trans + +| Prop name | Type | Description | +| --------- | -------- | ------------------- | +| `id` | `string` | Key, the message ID | + +:::important + +Import [`Trans`](/docs/ref/macro.md#jsxmacro-Trans) macro instead of [`Trans`](#trans) if you use macros: + +``` jsx +import { Trans } from "@lingui/macro" + +// Trans from @lingui/react won't work in this case +// import { Trans } from "@lingui/react" + +Hello, my name is {name} +``` +::: + +It's also possible to use `Trans` component directly without macros. In that case, `id` is the message being translated. `values` and `components` are arguments and components used for formatting translation: + +``` jsx +; + +; + +// number of tag corresponds to index in `components` prop +]} +/>; +``` + +#### Plurals + +If you cannot use [@lingui/macro](/docs/ref/macro.md) for some reason(maybe you compile your code using just TS instead of babel), you can render plurals using the plain Trans component like this: + +``` jsx +import React from 'react'; +import { Trans } from '@lingui/react'; + + +``` + +## Providers + +Message catalogs and the active locale are passed to the context in [`I18nProvider`](#i18nprovider). Use [`useLingui`](#uselingui) hook or [`withI18n`](#withi18n) high-order component to access Lingui context. + +### I18nProvider + +| Prop name | Type | Description | +| --------------------------- | --------------------- | ----------------------------------------------------------------------------- | +| `I18n` | `i18n` | The i18n instance (usually the one imported from `@lingui/core`) | +| `children` | `React.ReactNode` | React Children node | +| `defaultComponent` | `React.ComponentType` | A React component for rendering within this component (Not required) | +| `forceRenderOnLocaleChange` | `boolean` | Force re-render when locale changes (default: true) | + +`defaultComponent` has the same meaning as `component` in other i18n components. [`Rendering of translations`](#rendering-translations) is explained at the beginning of this document. + +``` jsx +import React from 'react'; +import { I18nProvider } from '@lingui/react'; +import { i18n } from '@lingui/core'; +import { messages as messagesEn } from './locales/en/messages.js'; + +i18n.load({ + en: messagesEn, +}); +i18n.activate('en'); + +const DefaultI18n = ({ isTranslated, children }) => ( + + {children} + +) + +const App = () => { + return ( + + // rest of the app + + ); +} +``` + +`forceRenderOnLocaleChange` is true by default and it ensures that: + +> - Children of `I18nProvider` aren't rendered before locales are +> loaded. +> - When locale changes, the whole element tree below `I18nProvider` +> is re-rendered. + +Disable `forceRenderOnLocaleChange` when you have specific needs to handle initial state before locales are loaded and when locale changes. + +This component should live above all i18n components. A good place is as a top-level application component. However, if the `locale` is stored in a `redux` store, this component should be inserted below `react-redux/Provider`: + +``` jsx +import React from 'react'; +import { I18nProvider } from '@lingui/react'; +import { i18n } from '@lingui/core'; +import { messages as messagesEn } from './locales/en/messages.js'; + +i18n.load({ + en: messagesEn, +}); +i18n.activate('en'); + +const App = () => { + return ( + + // rest of the app + + ); +} +``` + +### useLingui + +``` jsx +import React from "react" +import { useLingui } from "@lingui/react" + +const CurrentLocale = () => { + const { i18n } = useLingui() + + return Current locale: {i18n.locale} +} +``` + +### withI18n + +`withI18n` is a higher-order component which injects `i18n` object to wrapped component. `i18n` object is needed when you have to access the i18n data: + +``` jsx +import React from "react" +import { withI18n } from "@lingui/react" + +const CurrentLocale = withI18n()(({ i18n }) => ( + Current locale: {i18n.locale} +)) +``` diff --git a/website/docs/ref/snowpack-plugin.md b/website/docs/ref/snowpack-plugin.md new file mode 100644 index 000000000..99ae1d3a3 --- /dev/null +++ b/website/docs/ref/snowpack-plugin.md @@ -0,0 +1,56 @@ +# Snowpack Plugin + +It's a good practice to use compiled message catalogs during development. However, running [`compile`](/docs/ref/cli.md#compile) everytime messages are changed soon becomes tedious. + +`@lingui/snowpack-plugin` is a Snowpack plugin, which compiles messages on the fly: + +## Installation + +Install `@lingui/snowpack-plugin` as a development dependency: + +```bash npm2yarn +npm install --save-dev @lingui/snowpack-plugin +``` + +## Usage + +Simply add `@lingui/snowpack-plugin` inside your `snowpack.config.js`: + +``` js title="snowpack.config.js" +module.exports = { + plugins: [ + '@lingui/snowpack-plugin', + ], +} +``` + +Then in your code all you need is to use [dynamic imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports) to load only necessary catalog. Extension is mandatory. In case of using po format, use `.po`. + +``` ts +export async function dynamicActivate(locale: string) { + let catalog: {messages: Messages} + + switch (locale) { + case 'cs': + catalog = await import('./locales/cs/messages.po') + break + case 'en': + default: + catalog = await import('./locales/en/messages.po') + break; + } + + i18n.load(locale, catalog.messages) + i18n.activate(locale) +} +``` + +:::note +Comparing to [Webpack instructions for dynamic loading](/docs/ref/loader.md), code snippet above doesn't rely on variable `locale` to do the actual import. + +Instead, we manually check every possible value using `switch/case` and import final catalog by exact path. This is default behavior (or restriction?) of [esbuild](https://esbuild.github.io) - *extremely fast JavaScript bundler* used by Snowpack under the hood. + +There is [an issue regarding this feature](https://github.com/evanw/esbuild/issues/700) Similar restrictions apply to Babel macros or other non-standard features - they won't work with `esbuild` +::: + +See the [guide about dynamic loading catalogs](/docs/guides/dynamic-loading-catalogs.md) for more info. diff --git a/website/docs/releases/migration-3.md b/website/docs/releases/migration-3.md new file mode 100644 index 000000000..8d891c0e1 --- /dev/null +++ b/website/docs/releases/migration-3.md @@ -0,0 +1,240 @@ +# Migration guide from 2.x to 3.x + +:::caution Important +Check out the [@lingui/codemods](https://www.npmjs.com/package/@lingui/codemods) package for semi-automatic migration of your codebase. +::: + +## Backward incompatible changes + +Minimal required versions are: + +- Node.js: 10.x +- React: 16.8 +- Babel: 7 + +### `@lingui/react` + +- `` render-prop component was removed in favor of [`useLingui`](/docs/ref/react.md#uselingui) hook. +- In [`I18nProvider`](/docs/ref/react.md#i18nprovider), `defaultRender` prop was renamed to `defaultComponent`, and now only accepts Custom Components +- In [`Trans`](/docs/ref/react.md#Trans), `defaults` prop was renamed to `message` and `description` to `comment`. +- In [`Trans`](/docs/ref/react.md#Trans), `render` prop only accepts render-prop function which is used to render translation. +- In [`Trans`](/docs/ref/react.md#Trans), new prop `component` accepts React component which is used to render translation. +- In [`Trans`](/docs/ref/react.md#Trans), `components` is now an object, not an array. When using the low level API, it allows to name the component placeholders: + + ``` jsx + }} /> + ``` + +- `NumberFormat` and `DateFormat` components were removed. Import `i18n` from `@lingui/core` package and use [`i18n.date()`](/docs/ref/core.md#i18n.date) and [`i18n.number()`](/docs/ref/core.md#i18n.number) instead. + +#### Removed `I18nProvider` declarative API + +LinguiJS started as a React library. After `@lingui/core` package was introduced, there were two ways how to switch active locales and manage catalogs in React: either using [`I18nProvider`](/docs/ref/react.md#i18nprovider) declarative API or using `setupI18n` imperative API. + +In the same spirit as `@apollo/react` and `react-redux`, the [`I18nProvider`](/docs/ref/react.md#i18nprovider) is simplified and accepts `i18n` manager, which must be created manually: + +``` diff + import { I18nProvider } from '@lingui/react' + import { i18n } from "@lingui/core" ++ import { en } from 'make-plural/plurals' + import { messages } from './locale/en/messages.js' + ++ i18n.loadLocaleData('en', { plurals: en }) ++ i18n.load('en', messages) ++ i18n.activate('en') + + function App() { + return ( +- ++ + + + ) + } +``` + +### `@lingui/core` + +- Package now exports default `i18n` instance. It's recommended to use it unless you need customized instance. + + ``` diff + + import { i18n } from "@lingui/core" + - import { setupI18n } from "@lingui/core" + + - const i18n = setupI18n() + i18n.activate('en') + ``` + + :::caution Note + If you decide to use custom `i18n` instance, you also need to set [`runtimeConfigModule`](/docs/ref/conf.md#runtimeconfigmodule). Macros automatically import `i18n` instance and must be aware of correct import path. + ::: + +- `i18n.t`, `i18n.plural`, `i18n.select` and `i18n.selectOrdinal` methods were removed in favor of macros. + +- `i18n.use` was removed. Using two locales at the same time isn't common use-case and can be solved in user land by having two instances of `i18n` object. + +- Signature of [`i18n._`](/docs/ref/core.md#i18n._) method has changed. The third parameter now accepts default message in `message` prop, instead of `defaults`: + + ``` diff + - i18n._('Welcome / Greetings', { name: 'Joe' }, { defaults: "Hello {name}" }) + + i18n._('Welcome / Greetings', { name: 'Joe' }, { message: "Hello {name}" }) + ``` + +- [`i18n._`](/docs/ref/core.md#i18n._) also accepts a message descriptor as a first parameter: + + ``` diff + i18n._({ + id: string, + message?: string, + comment?: string + }) + ``` + +#### [`i18n.load`](/docs/ref/core.md#i18n.load) loads a catalog for a single locale + +`i18n` manager is the single source of truth and there's no need to keep all catalogs loaded outside this object. To make loading easier, [`i18n.load`](/docs/ref/core.md#i18n.load) now accepts catalog for a single locale or multiple catalogs at once. + +``` diff + import { i18n } from "@lingui/core" + import catalogEn from './locale/en/messages.js' + +- i18n.load({ en: catalogEn }) ++ i18n.load('en', catalogEn.messages) +``` + +:::caution Note +You can still use [`i18n.load`](/docs/ref/core.md#i18n.load(catalogs)) to load all catalogs at once: + +``` jsx +// i18n.js +import { i18n } from "@lingui/core" +import catalogEn from './locale/en/messages.js' +import catalogFr from './locale/fr/messages.js' + +i18n.load({ + en: catalogEn.messages, + fr: catalogFr.messages +}) +``` +::: + +### `@lingui/macro` + +- [`plural`](/docs/ref/macro.md#plural), [`select`](/docs/ref/macro.md#select) and [`selectOrdinal`](/docs/ref/macro.md#selectordinal) accepts value as a first parameter: + + ``` diff + - plural({ value, one: "# book", other: "# books" }) + + plural(value, { one: "# book", other: "# books" }) + ``` + +### `@lingui/cli` + +- command `lingui init` was removed +- command `lingui add-locale` was removed + +### Whitespace + +Whitespace handling in plugins had few bugs. By fixing them, there might be few backward incompatible changes. It's advised to run [`extract`](/docs/ref/cli.md#extract) and inspect changes in catalogs (if any). + +1. Spaces before `{variables}` in JSX aren't preserved. This is how React handles whitespaces in JSX. Leading whitespace is always removed: + + ``` jsx + + " + {variable} + " + + + // Becomes: "{variable}" + ``` + +2. Forced newlines are preserved. Sometimes it's useful to keep newlines in JSX. If that's your case, you need to force it in the same was as spaces are forced before variables or elements: + + ``` jsx + + 1. Item{"\n"} + 2. Item + + + // Becomes: 1. Item\n2. Item + ``` + +### Plugins/Presets + +Plugins are replaced with macros. Presets are removed completely because they aren't needed anymore. + +1. Uninstall plugins/presets, remove them from Babel config and replace them with `macros`: + + ```bash npm2yarn + npm uninstall @lingui/babel-preset-react + npm install --dev @lingui/macro babel-plugin-macros + ``` + + ```diff + { + "presets": [ + - "@lingui/babel-preset-react" + ], + "plugins": [ + + "macros", + ] + } + ``` + +2. Import [`Trans`](/docs/ref/macro.md#trans), [`Plural`](/docs/ref/macro.md#plural-1), [`Select`](/docs/ref/macro.md#select-1) and [`SelectOrdinal`](/docs/ref/macro.md#selectordinal-1) from `@lingui/macro`: + + ```diff + - import { Trans } from "@lingui/react" + + import { Trans } from "@lingui/macro" + ``` + + :::caution Note + If you used [`Trans`](/docs/ref/macro.md#trans) component without children, then keep the import from `@lingui/react`: + + ```jsx + import { Trans } from "@lingui/react" + + const CustomID = () => + const DynamicID = () => + ``` + ::: + +3. `i18n.t`, `i18n.plural`, `i18n.select` and `i18n.selectOrdinal` methods are removed and replaced with macros. + + These macros automatically binds message to default `i18n` object: + + ``` diff + import { i18n } from "@lingui/core" + + import { t } from "@lingui/macro" + + - i18n.t`Hello World` + + t`Hello World` + ``` + +## New features + +### [`i18n.load`](/docs/ref/core.md#i18n.load) + +[`i18n.load`](/docs/ref/core.md#i18n.load) can now accept one catalog for specific locale. Useful for incremental loading of catalogs. + +```jsx +import { i18n } from "@lingui/core" + +// Lingui v2 and v3 +i18n.load({ + en: require("./locale/en/messages"), + cs: require("./locale/cs/messages") +}) + +// Lingui v3 only +i18n.load('en', require("./locale/en/messages")) +i18n.load('cs', require("./locale/cs/messages")) +``` + +### `i18n.on('change', callback)` + +Event [`change`](/docs/ref/core.md#change) is fired anytime new catalogs are loaded or when locale is activated. + +### Native TypeScript support + +Lingui now supports TypeScript out of the box, don't forget to remove the `@types/lingui` packages from your project. diff --git a/website/docs/tools/crowdin.md b/website/docs/tools/crowdin.md new file mode 100644 index 000000000..6e329af48 --- /dev/null +++ b/website/docs/tools/crowdin.md @@ -0,0 +1,150 @@ +# Crowdin + +Crowdin agile localization for developers +
+ +Crowdin is a localization management platform that helps translate your LinguiJS-based product. Automate localization, release several multilingual versions of your app simultaneously, and provide an enhanced experience for your global customers. + +[Website](https://crowdin.com/) \| [GitHub](https://github.com/crowdin) \| [Support](https://crowdin.com/contacts) + +## Features + +### Keep all translations in one place while connecting your teams via Crowdin + +Connect with your content, marketing, and translation teams in one collaborative space: + +- Screenshots for additional context. +- Highlight HTML, placeholders, plurals, and more. +- Describe the context and set character limits to ensure the translation fits the UI. +- All translations are done online or can be uploaded to the platform. +- Jira integration to notify you about source string issues. +- Tips for translators to ensure there is no extra space or broken code. + +### Ship faster with localization running in parallel + +Keep developing new features and improvements while translators receive new texts in real-time. Release multilingual versions for customers around the globe simultaneously. + +### Release your product in several languages at once + +Help users from different regions use the latest version of your product in their language: + +- Get feature branches translated independently from the master branch. +- Translators work together in one place to boost productivity. +- Never deal with translations in spreadsheets or email attachments. +- Source texts are updated for translators automatically and in real-time. +- Automatically pull completed translations that are ready to be merged. + +### Seamlessly integrate localization during any phase of your development cycle + +Automate the integration of source texts and translations between Crowdin and your source code with one-click integration or customizable solutions. + +### Define your translation strategy + +Decide who will translate your content: + +- Invite your team of translators (in-house translators, freelancers, or translation agencies you already work with). +- Order professional translations from a vendor (translation agency) from Crowdin Vendors Marketplace. +- Configure machine translation engines. +- Engage your community. + +### VCS: GitHub, GitLab, Bitbucket + +Source strings are pulled automatically and are always up to date for your translators. Translated content is automatically pushed to your repository as a request. + +![Automatically pull source strings to Crowdin and push translated content to your repository](/img/docs/Crowdin__js-lingui-vcs.png) + +### CLI + +Easily integrate Crowdin with your CI server, GIT, SVN, Mercurial, etc. Connect cross-platform [Crowdin CLI](https://support.crowdin.com/cli-tool/) directly to your code repository and never deal with localization files manually again. + +![Manage and synchronize your localization resources with Crowdin CLI](/img/docs/Crowdin__js-lingui-cli.png) + +### 1. Create the `crowdin.yml` configuration file + +```yaml title="crowdin.yml" +project_id: '123456' # Your Crowdin project ID +api_token_env: CROWDIN_PERSONAL_TOKEN + +preserve_hierarchy: true + +files: # Paths to the source and translation files + - source: /**/locales/en/* + translation: /**/locales/%two_letters_code%/%original_file_name% +``` + +### 2. Install the Crowdin CLI as an npm package + +```bash npm2yarn +npm install @crowdin/cli@3 +``` + +### 3. Add the following scripts + +Add these lines to your `package.json` to make your life easier. + +```js title="package.json" +{ + "scripts": { + "crowdin": "crowdin", + "sync": "crowdin push && crowdin pull", + "sync:sources": "crowdin push", + "sync:translations": "crowdin pull" + } +} +``` + +### 4. Configuration + +Set the `CROWDIN_PERSONAL_TOKEN` env variable on your computer, to allow the CLI to authenticate with the Crowdin API. + +### 5. Usage + +Test that you can run the Crowdin CLI: + +```bash npm2yarn +npm run crowdin --version +``` + +Upload all the source files to Crowdin: + +```bash npm2yarn +npm run sync:sources +``` + +Download translation files from Crowdin: + +```bash npm2yarn +npm run sync:translations +``` + +Upload sources to Crowdin and download translations from Crowdin: + +```bash npm2yarn +npm run sync +``` + +To run other Crowdin CLI commands you can use the following command: + +```bash npm2yarn +npm run crowdin +``` + +To see the full list of possible commands and options: + +```bash npm2yarn +npm run crowdin -h +``` + +### [API](https://support.crowdin.com/api/v2/) and webhooks + +Customize your experience. Automate and scale your localization workflow. Seamlessly add new content for translation to your Crowdin project, check translation status, merge new content, etc. + +### To get started, register a [Crowdin.com](https://accounts.crowdin.com/register) or [Crowdin Enterprise](https://accounts.crowdin.com/workspace/create) account + +Once you have signed up, we recommend [creating your localization project](https://support.crowdin.com/creating-project/). + +Depending on the ways you would like to work with Crowdin, we offer such options: + +1. [Integrate Crowdin with GitHub](https://support.crowdin.com/github-integration/). +2. Manage and synchronize your localization resources with [Crowdin CLI](https://support.crowdin.com/cli-tool/). +3. [Upload files for the test via UI](https://support.crowdin.com/uploading-files/). diff --git a/website/docs/tools/introduction.md b/website/docs/tools/introduction.md new file mode 100644 index 000000000..6a5f359a2 --- /dev/null +++ b/website/docs/tools/introduction.md @@ -0,0 +1,68 @@ +# Introduction + +## Why use Sync & Collaboration Tools? + +The easiest way to translate your application is to translate the `.po` files directly in a text editor, or with a tool like [Poedit](https://poedit.net). + +This solution may be good enough when your application is still small and doesn't evolve much, but it quickly becomes hard work when the number of sentences to translate and the target languages to manage increase with time. + +It then becomes more and more difficult and time-consuming to manage the back and forth with the translators, while keeping your application `.po` files up-to-date with the current state of a codebase that doesn't stop evolving. + +That's why sync and collaboration tools are directly integrated into Lingui to help structure your work with your translators team, to ensure the consistency of the translations, and to make your life easier. + +### Regular Workflow + +![Translation workflow \*without\* sync and collaboration tool](/img/docs/without-collaboration-tool.png) + +This is the most basic workflow which involves sending the `.po` files to your translators (usually by email) and syncing them back manually into your application. + +This workflow is manageable when your application is still quite small, doesn't contain a lot of text, and doesn't evolve much. + +### Sync & Collaboration Tool Workflow + +![Translation workflow \*with\* sync and collaboration tool](/img/docs/with-collaboration-tool.png) + +When the amount of text to translate increases, and the number of target languages grows, it becomes more efficient to use a sync and collaboration tool to assist you with the management of your team of translators, and co-evolution between your code and the translated files. + +Instead of manually sending and receiving many emails and fixing the inconsistencies with your code, a `sync` method is called and your `.po` and `.js` files are directly updated with the latest translations. Your translators will also be notified when there are new text to translate. + +## Benefits of these tools + +- **Synchronization**: unique `yarn sync` or `npm run sync` command to synchronize your project with all your translators and update your local `.po` and `.js` files with the latest translations. +- **Translation Interface**: provide a professional and flexible interface to translators. +- **Translation Memory**: assist translators by suggesting previously translated sentences that are similar. +- **Machine Translation**: auto-translate with Google Translate, DeepL, etc. and human-proofread later. +- **Smart Plural Management**: allows to translate `Message` and `Messages` instead of `{count, plural, one {Message} other {Messages}}`. +- **Consistency**: assist translators with `{variable}` interpolation and HTML formatting. + +## Configure your project + +To synchronize your current application with an online tool, you just have to add these lines at the end of your `.linguirc` configuration file: + +```js title=".linguirc" +{ + [...] + "service": { + "name": "ToolName", + "apiKey": "abcdefghijklmnopqrstuvwxyz012345" + } +} +``` + +The synchronization will then be part of the [`extract`](/docs/ref/cli.md#extract) command. + +## List of available tools + +### Crowdin () + +- Documentation: [Crowdin](/docs/tools/crowdin.md) +- GitHub: + +### Translation.io () + +- Documentation: [translation-io](/docs/tools/translation-io.md) +- GitHub: + +## Adding a new tool to Lingui + +If you want to integrate a new tool with Lingui, you have to add your synchronization workflow in a new file located in [this directory](https://github.com/lingui/js-lingui/tree/main/packages/cli/src/services) and then create a pull request on the main Lingui project. diff --git a/website/docs/tools/translation-io.md b/website/docs/tools/translation-io.md new file mode 100644 index 000000000..72d94d787 --- /dev/null +++ b/website/docs/tools/translation-io.md @@ -0,0 +1,158 @@ +# Translation.io + +Translation.io Lingui Logo +
+ +[Translation.io](https://translation.io/lingui) is a professional synchronization and collaboration platform that will assist your team in the translation of your Lingui application. + +Links: + +- Website: +- GitHub: + +## Features + +### Smooth Team Management + +Invite your collaborators using their email or username, and assign them a role and a target language. We'll bring them on board and keep them informed about any new activity in their language. + +![Smooth Team Management on Translation.io](https://translation.io/gifs/lingui/translation-collaborators.gif) + +Learn more: + +- [Fine-Grained Authorizations](https://translation.io/blog/fine-grained-authorization-and-role-management?default_stack=lingui) +- [Activity Digests](https://translation.io/blog/better-history-and-activity-email-digests?default_stack=lingui) + +### Elegant Translation Process + +Our interface was designed to be the most ergonomic way to translate. It provides translation suggestions (from [TM](https://en.wikipedia.org/wiki/Translation_memory), Google Translate or DeepL), context, discussion and history. + +Keyboard shortcuts allow translators to stay focused on their work, visual hints indicate when something went wrong, for example when an interpolated variable or HTML tag is missing. + +![Elegant Translation Process on Translation.io](https://translation.io/gifs/lingui/translation-interface.gif) + +Learn more: + +- [Keyboard Shortcuts](https://translation.io/blog/shortcuts-and-translation?default_stack=lingui) +- [History and Activity Digests](https://translation.io/blog/better-history-and-activity-email-digests?default_stack=lingui) + +### Syntax Highlighting + +Sometimes you have no choice but to confront your translators with HTML or interpolated variables. The problem is that translators do not necessarily know the meaning of these notations and may be tempted to translate them or may inadvertently alter them. + +`Hello {name}` should never be translated as `Bonjour {nom}`, and we have several mechanisms to ensure that, like warnings and auto-completion: + +![Syntax Highlighting warning on Translation.io](https://translation.io/_articles/2019-10-11-highlighting-of-html-tags-and-interpolated-variables/highlight-interpolated-variable-lingui.png) + +--- + +![Syntax Highlighting auto-completion on Translation.io](https://translation.io/gifs/lingui/translation-highlights.gif) + +### Smart Plural Management + +Lingui allows to write plurals using the [ICU MessageFormat](/docs/ref/message-format.md) syntax that looks like this: + +``` none +{count, plural, =0 {No messages} + one {# message} + other {# messages}} +``` + +But you can't ask a translator to understand this syntax, and he or she would be tempted to translate `one` or `other` keywords in other languages, breaking your code at the same time. + +That's why we deconstruct the plural syntaxes to make them easy to translate, and then reconstruct them inside your local `.po` files. + +If the target language has more plural forms than the source language, we also provide some examples to the translator, because it could be unclear what plural form the `few` or `other` keyword may refer to in that specific target language (in this example, Czech has 3 plural forms). + +![Smart Plural Management on Translation.io](/img/docs/translation-lingui-plural-forms.png) + +### Efficient Search + +Our powerful search helps translators to maintain consistency of terms throughout their work. In addition, they are able to filter depending on a particular source file or context. To provide a more enjoyable experience, this lightning-fast search works without any page reloading. + +![Efficient Search on Translation.io](https://translation.io/gifs/lingui/translation-search.gif) + +Learn more: + +- [Smart URLs](https://translation.io/blog/smart-urls-in-translation-interface?default_stack=lingui) + +### Adaptive Workflows using Tags + +Our interface is flexible enough to adapt to your own translation workflows. Add custom tags to your segments and you'll be directly able to filter them. Moreover, these tags will appear in the statistics page so you can use them for reporting. + +![Adaptive Workflows using Tags on Translation.io](https://translation.io/gifs/lingui/translation-tags.gif) + +Learn more: + +- [How to Use Tags](https://translation.io/blog/tags-work-better-as-a-team?default_stack=lingui) +- [Project Statistics](https://translation.io/blog/translation-project-statistics?default_stack=lingui) + +## Installation + +### 1. Create your Lingui project + +Create an account on [Translation.io](https://translation.io/lingui) and create a new Lingui project. + +### 2. Configure your application + +Copy the `.linguirc` configuration file that was generated for you to the root of your application. + +The configuration file looks like this: + +```js title=".linguirc" +{ + [...] + "format": "po", + "service": { + "name": "TranslationIO", + "apiKey": "abcdefghijklmnopqrstuvwxyz012345" + } +} +``` + +### 3. Add the following scripts + +Add these lines to your `package.json` to make your life easier. + +```js title="package.json" +{ + "scripts": { + "sync": "lingui extract --overwrite && lingui compile", + "sync_and_purge": "lingui extract --overwrite --clean && lingui compile" + } +} +``` + +### 4. Initialize your project + +Initialize your project and upload your source text and potential existing translations with: + +```bash npm2yarn +npm run sync +``` + +## Usage + +### Sync + +To send new translatable strings and get new translations from Translation.io, and at the same time generate the minified JavaScript catalog files, simply run: + +```bash npm2yarn +npm run sync +``` + +### Sync and Purge + +If you need to remove unused strings from Translation.io, using the current branch as reference. + +As the name says, this operation will also perform a sync at the same time. + +**Warning**: all strings that are not present in the current local branch will be **permanently deleted from Translation.io**. + +```bash npm2yarn +npm run sync_and_purge +``` + +--- + +If you need some help with your project, feel free to contact diff --git a/website/docs/tutorials/cli.md b/website/docs/tutorials/cli.md new file mode 100644 index 000000000..e64fa6425 --- /dev/null +++ b/website/docs/tutorials/cli.md @@ -0,0 +1,167 @@ +# Working with LinguiJS CLI + +`@lingui/cli` provides the `lingui` command for extracting, merging and compiling message catalogs. Follow [setup instructions](/docs/ref/cli.md) to install required packages. + +## Extracting messages + +We're going to use an app we built in a [React tutorial](/docs/tutorials/react.md). The [`extract`](/docs/ref/cli.md#extract) command looks for messages in the source files and extracts them: + +```bash npm2yarn +> npm run extract + +Extracting messages from source files… +Collecting all messages… +Writing message catalogs… +Messages extracted! + +Catalog statistics: +┌──────────┬─────────────┬─────────┐ +│ Language │ Total count │ Missing │ +├──────────┼─────────────┼─────────┤ +│ cs │ 40 │ 40 │ +│ en │ 40 │ 40 │ +└──────────┴─────────────┴─────────┘ + +(use "yarn extract" to update catalogs with new messages) +(use "yarn compile" to compile catalogs for production) +``` + +The message catalog will look like this: + +```json +{ + "Message Inbox": "", + "See all <0>unread messages or <1>mark them as read.": "", + "{messagesCount, plural, one {There's {messagesCount} message in your inbox.} other {There are {messagesCount} messages in your inbox.}}": "", + "Last login on {lastLogin,date}.": "", +} +``` + +It's in a JSON dictionary, where 'key' is message ID and value is an object with some relevant information: translation, defaults and origin for the message. + +This catalog is ready for translation. Let's translate it into Czech by filling the `translation` fields: + +```json +{ + "Message Inbox": "Přijaté zprávy", + "See all <0>unread messages or <1>mark them as read.": "Zobrazit všechny <0>nepřečtené zprávy nebo je <1>označit jako přečtené.", + "{messagesCount, plural, one {There's {messagesCount} message in your inbox.} other {There are {messagesCount} messages in your inbox.}}": "{messagesCount, plural, one {V příchozí poště je {messagesCount} zpráva.} few {V příchozí poště jsou {messagesCount} zprávy. } other {V příchozí poště je {messagesCount} zpráv.}}", + "Last login on {lastLogin,date}.": "Poslední přihlášení {lastLogin,date}", +} +``` + +If we run the [`extract`](/docs/ref/cli.md#extract) command again, we can see in the stats that all messages are translated: + +```bash npm2yarn +> npm run extract + +Catalog statistics: +┌──────────┬─────────────┬─────────┐ +│ Language │ Total count │ Missing │ +├──────────┼─────────────┼─────────┤ +│ cs │ 4 │ 0 │ +│ en │ 4 │ 4 │ +└──────────┴─────────────┴─────────┘ + +Messages extracted! + +(use "yarn extract" to update catalogs with new messages) +(use "yarn compile" to compile catalogs for production) +``` + +[`extract`](/docs/ref/cli.md#extract) merges all translations with new messages, so you can run this command any time without worrying about losing any translations. + +## Preparing catalogs for production + +Once we have all catalogs ready and translated, we can compile the JSON into a minified JS file with the [`compile`](/docs/ref/cli.md#compile) command. This command parses the messages in MessageFormat and compiles them into simple functions. It also adds plural rules to a production ready catalog: + +```bash npm2yarn +> npm run compile + +Compiling message catalogs… +Done! +``` + +The `locale` directory now contains the source catalogs (`messages.json`) and the compiled ones (`messages.js`). + +Messages added to compiled file are collected in specific order: + +1. Translated message from specified locale +2. Translated message from fallback locale for specified locale +3. Translated message from default fallback locale +4. Message key + +It is also possible to merge the translated catalogs into a single file per locale by specifying `catalogsMergePath`. For example if `catalogsMergePath` is assigned `locales/{locale}` then catalogs will be compiled to `/locales/cs.js` and `/locales/en.js`. + +## Cleaning up obsolete messages + +By default, the [`extract`](/docs/ref/cli.md#extract) command merges messages extracted from source files with the existing message catalogs. This is safe as we won't accidentally lose translated messages. + +However, sooner or later some messages will be removed from the source. We can use the [`--clean`](/docs/ref/cli.md#extract-clean) option to clean up our message catalogs: + +```bash npm2yarn +npm run extract --clean +``` + +## Validation of message catalogs + +It might be useful to check if all messages were translated (e.g: in a continuous integration runner). The [`compile`](/docs/ref/cli.md#compile) command has a [`--strict`](/docs/ref/cli.md#compile-strict) option, which does exactly that. + +The example output might look like this: + +```bash npm2yarn +> npm run compile --strict + +Compiling message catalogs… +Error: Failed to compile catalog for locale en! +Missing 42 translation(s) +``` + +## Configuring source locale + +We see that checking for missing translations has one drawback -- English message catalog doesn't require any translations because we're using English in our source code! + +Let's fix it by setting [`sourceLocale`](/docs/ref/conf.md#sourcelocale) in `package.json`: + +```json title="package.json" +{ + "lingui": { + "sourceLocale": "en" + } +} +``` + +Running `extract` again shows the correct statistics: + +```bash npm2yarn +> npm run extract + +Catalog statistics: +┌─────────────┬─────────────┬─────────┐ +│ Language │ Total count │ Missing │ +├─────────────┼─────────────┼─────────┤ +│ cs │ 4 │ 0 │ +│ en (source) │ 4 │ - │ +└─────────────┴─────────────┴─────────┘ +``` + +And compilation in strict mode no longer throws an error: + +```bash npm2yarn +> npm run compile --strict + +Compiling message catalogs… +Done! +``` + +If you use natural language for message IDs (that's the default), set [`sourceLocale`](/docs/ref/conf.md#sourcelocale). You shouldn't use this config if you're using custom IDs (e.g: `Component.title`). + +## Catalogs in VCS and CI + +The `locale/_build` folder and `locale/*/*.js` (compiled catalogs) are safe to be ignored by your VCS. What you do need to keep in VCS are the json files (`locale/*/*.json`) that contain the messages for translators. The JavaScript functions that return the actual translations when your app runs in production are created from those json files. See [Excluding build files](/docs/guides/excluding-build-files.md) guide for more info. + +If you're using a CI, it is a good idea to add the `yarn extract` and `yarn compile` commands to your build process. + +## Further reading + +That's it! Checkout [CLI Reference](/docs/ref/cli.md) documentation for more info about `lingui` commands or [configuration reference](/docs/ref/conf.md) for info about configuration parameters. diff --git a/website/docs/tutorials/javascript.md b/website/docs/tutorials/javascript.md new file mode 100644 index 000000000..e26a0f86d --- /dev/null +++ b/website/docs/tutorials/javascript.md @@ -0,0 +1,107 @@ +# Internationalization of JavaScript apps + +In this tutorial, we'll learn how to use LinguiJS's internationalization features that do not depend on React. We'll take a minimalist approach and cover the main functions from the `@lingui/core` package. + +## Installing LinguiJS + +[LinguiJS](https://github.com/lingui/js-lingui) isn't just a package. It's a set of tools which helps you to internationalize. You can pick whichever tool you want to use in your project. We're trying to use most of them to show the full power of LinguiJS. + +Let's start with the three major packages: + +`@lingui/cli` + +> CLI for i18n management and working with message catalogs + +`@lingui/core` + +> The core library responsible for translation and handling of message catalogs + +`@lingui/macro` + +> Transforms messages wrapped in tagged template literals to ICU MessageFormat and validates them. + +1. Install `@lingui/cli`, `@lingui/macro`, `babel-plugin-macros` and Babel core packages as a development dependencies and `@lingui/core` as a runtime dependency: + + ```bash npm2yarn + npm install --save-dev @lingui/cli @lingui/macro babel-plugin-macros @babel/core + npm install --save @lingui/core + ``` + +2. Add `macros` plugin to Babel config (e.g: `.babelrc`): + + ``` json + { + "plugins": [ + "macros" + ] + } + ``` + + Now we have the environment up and running and we can start internationalizing our app! + +## Setting up i18n + +First we need to setup the i18n singleton, which is pretty simple: + +``` js +import { i18n } from '@lingui/core' + +// messages.js is generated by the cli +import { messages } from 'path-to-locale/en/messages.js'; + +i18n.load('en', messages) +i18n.activate('en') +``` + +## Localizing your app + +Once that is done, we can go ahead and use it! Wrap you text in [`t`](/docs/ref/macro.md#t) macro and pass it to [`i18n._()`](/docs/ref/core.md#i18n._) method: + +``` js +import { t } from "@lingui/macro" + +t`Hello World!` +// becomes "Salut le monde!" + +const name = "Fred" +t`My name is ${ name }` +// becomes "Je m'appelle Fred" +``` + +Plurals and selections are possible using plural and select methods: + +``` js +import { plural } from "@lingui/macro" + +const count = 42 + +plural(count, { + one: "# book", + other: "# books" +}) +// becomes "42 livres" +``` + +It's also possible to nest message formats. Each message format method in i18n has a standalone companion, which only returns message without performing the translation: + +``` js +import { t, select, plural } from "@lingui/macro" + +select(gender, { + offset: 1, + female: plural(numOfGuests, { + offset: 1, + 0: t`${host} does not give a party.`, + 1: t`${host} invites ${guest} to her party.`, + 2: t`${host} invites ${guest} and one other person to her party.`, + other: t`${host} invites ${guest} and # other people to her party.` + }), + male: plural(value, {...}), + other: plural(value, {...}), +}) +``` + +## Further reading + +- [`@lingui/cli` reference documentation](/docs/ref/cli.md) +- [Pluralization Guide](/docs/guides/plurals.md) diff --git a/website/docs/tutorials/react-native.md b/website/docs/tutorials/react-native.md new file mode 100644 index 000000000..3a87a3f33 --- /dev/null +++ b/website/docs/tutorials/react-native.md @@ -0,0 +1,222 @@ +# Internationalization of React Native apps + +In this tutorial, we'll learn how to add internationalization to an existing application in React Native. The React Native tutorial is largely similar to the one for [React](/docs/tutorials/react.md), and we highly recommend you check out that tutorial first because it covers installation, setup and other topics. Here we will cover parts that are relevant for React Native and hopefully answer all questions you may have. + +:::caution Note +The latest version of `@lingui/react` working out-of-the-box for React Native on Android is 2.2. Newer versions depend on the [Intl object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) which is not available on the JavaScript Core that is used on Android by default. See the [JSC build scripts for Android](https://github.com/react-community/jsc-android-buildscripts) for possible solution or use the [Intl polyfill](https://github.com/andyearnshaw/Intl.js/). +::: + +If you're looking for a working solution, check out the [demo on Expo](https://exp.host/@vonovak/js-lingui-demo). The source code is [available here](https://github.com/vonovak/js-lingui-demo). + +## Let's Start + +We're going to translate the following app: + +```jsx +import React from 'react'; +import { StyleSheet, Text, View, Alert, SafeAreaView, Button } from 'react-native'; + +export default class App extends React.Component { + render() { + return ( + + ); + } + + showAlert = () => { + Alert.alert('', 'Do you want to set all your messages as read?'); + }; +} + +const Inbox = ({ messages, markAsRead, username }) => { + const messagesCount = messages.length; + + return ( + + + Message Inbox + + + See all unread messages or + + +} + +export function LoginLogoutButtons(props) { + return
+ Log in} /> + Log out} /> +
+} +``` + +If you need the prop to be displayed as a string-only translation, you can pass a message tagged with the [`t`](/docs/ref/macro.md#t) macro: + +```jsx +import { t } from "@lingui/macro" + +export default function ImageWithCaption(props) { + return {props.caption} +} + +export function HappySad(props) { + return
+ + +
+} +``` + +### Picking a message based on a variable + +Sometimes you need to pick between different messages to display, depending on the value of a variable. For example, imagine you have a numeric "status" code that comes from an API, and you need to display a message representing the current status. + +A simple way to do this, is to make an object that maps the possible values of "status" to message descriptors (tagged with the [`defineMessage`](/docs/ref/macro.md#definemessage) macro), and render them as needed with lazy translation: + +```jsx +import { defineMessage, Trans } from "@lingui/macro"; + +const STATUS_OPEN = 1, + STATUS_CLOSED = 2, + STATUS_CANCELLED = 4, + STATUS_COMPLETED = 8 + +const statusMessages = { + [STATUS_OPEN]: defineMessage({message: "Open"}), + [STATUS_CLOSED]: defineMessage({message: "Closed"}), + [STATUS_CANCELLED]: defineMessage({message: "Cancelled"}), + [STATUS_COMPLETED]: defineMessage({message: "Completed"}), +} + +export default function StatusDisplay({ statusCode }) { + return
+} +``` diff --git a/website/docs/tutorials/react.md b/website/docs/tutorials/react.md new file mode 100644 index 000000000..e1355744e --- /dev/null +++ b/website/docs/tutorials/react.md @@ -0,0 +1,719 @@ +# Internationalization of React apps + +Through this tutorial, we'll learn how to add internationalization (i18n) to an existing application in React JS. + +## Let's Start + +We're going to translate the following app: + +```jsx title="src/index.js" +import React from 'react' +import { render } from 'react-dom' +import Inbox from './Inbox' + +const App = () => + +render(, document.getElementById('root')) +``` + +```jsx title="src/Inbox.js" +import React from 'react' + +export default function Inbox() { + const messages = [{}, {}] + const messagesCount = messages.length + const lastLogin = new Date() + const markAsRead = () => { alert('Marked as read.') } + + return ( +
+

Message Inbox

+ +

+ See all unread messages{" or "} + mark them as read. +

+ +

+ { + messagesCount === 1 + ? `There's ${messagesCount} message in your inbox.` + : `There are ${messagesCount} messages in your inbox.` + } +

+ +
+ Last login on {lastLogin.toLocaleDateString()}. +
+
+ ) +} +``` + +As you can see, it's a simple mailbox application with only one page. + +## Installing LinguiJS + +Follow setup guide either for projects using [LinguiJS with Create React App](/docs/tutorials/setup-cra.md) or for general [React projects](/docs/tutorials/setup-react.md). + +## Setup + +We will directly start translating the `Inbox` component, but we need to complete one more step to setup our application. + +Components need to read information about current language and message catalogs from `i18n` instance. Initially, you can use the one created and exported from `@lingui/core` and later you can replace with your one if such need arise. + +In order to pass `i18n` around the I18nProvider wraps around React Context. + +Let's add all required imports and wrap our app inside [`I18nProvider`](/docs/ref/react.md#i18nprovider): + +```jsx title="src/index.js" +import React from 'react' +import { render } from 'react-dom' + +import { i18n } from '@lingui/core' +import { I18nProvider } from '@lingui/react' +import { messages } from './locales/en/messages' +import Inbox from './Inbox' + +i18n.load('en', messages) +i18n.activate('en') + +const App = () => ( + + + +) + +render(, document.getElementById('root')) +``` + +:::tip +You might be wondering: how are we going to change the active language? That's what the [`I18n.load`](/docs/ref/core.md#i18n.load) and [`i18n.activate`](/docs/ref/core.md#i18n.activate) calls are for! However, we cannot change the language unless we have the translated message catalog. And to get the catalog, we first need to extract all messages from the source code. + +Let's deal with language switching later... but if you're still curious, take a look at [example](/docs/guides/dynamic-loading-catalogs.md) with Redux and Webpack. +::: + +## Introducing internationalization + +Now we're finally going to *translate* our app. Actually, we aren't going to *translate* from one language to another right now. Instead, we're going to *prepare* our app for translation. This process is called *internationalization* and you should practice saying this word aloud until you're able to say it three times very quickly. + +:::note +From now on, *internationalization* will be shortened to a common numeronym *i18n*. +::: + +Let's start with the basics - static messages. These messages don't have any variables, HTML or components inside. Just some text: + +``` jsx +

Message Inbox

+``` + +All we need to make this heading translatable is wrap it in [`Trans`](/docs/ref/macro.md#trans) macro: + +``` jsx +import { Trans } from '@lingui/macro' + +

Message Inbox

+``` + +### Macros vs. Components + +If you're wondering what Babel macros are and what's the difference between macros and components, this short paragraph is for you. + +In general, macros are executed at compile time and they transform source code in some way. We use this feature in [LinguiJS](https://github.com/lingui/js-lingui) to simplify writing messages. + +Under the hood, all JSX macros are transformed into [`Trans`](/docs/ref/react.md#trans) component. Take a look at this short example. This is what we write: + +``` jsx +import { Trans } from '@lingui/macro' + +Hello {name} +``` + +And this is how the code is transformed: + +``` jsx +import { Trans } from '@lingui/react' + + +``` + +See the difference? [`Trans`](/docs/ref/react.md#trans) component receives `id` prop with a message in ICU MessageFormat syntax. +We could write it manually, but it's just easier and shorter to write JSX as we're used to and let macros to generate message for ourselves. + +### Extracting messages + +Back to our project. It's nice to use JSX and let macros generate messages under the hood. Let's check that it actually works correctly. + +All messages from the source code must be extracted into external message catalogs. Message catalogs are interchange files between developers and translators. We're going to have one file per language. Let's enter command line for a while. + +We're going to use [CLI](/docs/ref/cli.md) again. Run [`extract`](/docs/ref/cli.md#extract) command to extract messages: + +```bash +> lingui extract + +No locales defined! + +Add 'locales' to your configuration. See https://lingui.js.org/ref/conf.html#locales +``` + +We need here to fix the configuration. Create a `.linguirc` file: + +```json title=".linguirc" +{ + "locales": ["cs", "en"], + "catalogs": [{ + "path": "src/locales/{locale}/messages", + "include": ["src"] + }] +} +``` + +After fixing configuration, let's run [`extract`](/docs/ref/cli.md#extract) command again: + +```bash +> lingui extract + +Catalog statistics: +┌──────────┬─────────────┬─────────┐ +│ Language │ Total count │ Missing │ +├──────────┼─────────────┼─────────┤ +│ cs │ 1 │ 1 │ +│ en │ 1 │ 1 │ +└──────────┴─────────────┴─────────┘ + +(use "lingui extract" to update catalogs with new messages) +(use "lingui compile" to compile catalogs for production) +``` + +Nice! It seems it worked, we have two message catalogs (one per each locale) with 1 message each. Let's take a look at file `src/locales/cs/messages.po`: + +```gettext title="src/locales/cs/messages.po" +msgid "" +msgstr "" +"POT-Creation-Date: 2021-07-22 21:44+0900\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: @lingui/cli\n" +"Language: cs\n" + +#: src/Inbox.js:12 +msgid "Message Inbox" +msgstr "" +``` + +That's the message we've wrapped inside [`Trans`](/docs/ref/macro.md#trans) macro! + +Let's add a Czech translation: + +```po title="src/locales/cs/messages.po" +#: src/Inbox.js:12 +msgid "Message Inbox" +msgstr "Příchozí zprávy" +``` + +If we run [`extract`](/docs/ref/cli.md#extract) command again, we'll see that all Czech messages are translated: + +```bash +> lingui extract + +Catalog statistics: +┌──────────┬─────────────┬─────────┐ +│ Language │ Total count │ Missing │ +├──────────┼─────────────┼─────────┤ +│ cs │ 1 │ 0 │ +│ en │ 1 │ 1 │ +└──────────┴─────────────┴─────────┘ + +(use "lingui extract" to update catalogs with new messages) +(use "lingui compile" to compile catalogs for production) +``` + +That's great! So, how we're going to load it into your app? [LinguiJS](https://github.com/lingui/js-lingui) introduces concept of compiled message catalogs. Before we load messages into your app, we need to compile them. As you see in the help in command output, we use [`compile`](/docs/ref/cli.md#compile) for that: + +```bash +> lingui compile + +Compiling message catalogs… +Done! +``` + +What just happened? If you look inside `locales/` directory, you'll see there's a new file for each locale: `messages.js`. This file contains compiled message catalog. + +Let's load this file into our app and set active language to `cs`: + +```jsx title="src/index.js" {6-7,10-14} +import React from 'react' +import { render } from 'react-dom' + +import { i18n } from '@lingui/core' +import { I18nProvider } from '@lingui/react' +import { messages as enMessages } from './locales/en/messages' +import { messages as csMessages } from './locales/cs/messages' +import Inbox from './Inbox' + +i18n.load({ + en: enMessages, + cs: csMessages, +}) +i18n.activate('cs') + +const App = () => ( + + + +) + +render(, document.getElementById('root')) +``` + +When we run the app, we see the inbox header is translated into Czech. + +### Summary of basic workflow + +Let's go through the workflow again: + +1. Add an [`I18nProvider`](/docs/ref/react.md#i18nprovider), this component provides the active language and catalog(s) to other components +2. Wrap messages in [`Trans`](/docs/ref/macro.md#trans) macro +3. Run [`extract`](/docs/ref/cli.md#extract) command to generate message catalogs +4. Translate message catalogs (send them to translators usually) +5. Run [`compile`](/docs/ref/cli.md#compile) to create runtime catalogs +6. Load runtime catalog +7. Profit + +Steps 1 and 7 needs to be done only once per project and locale. Steps 2 to 5 become the common workflow for internationalizing the app. + +It isn't necessary to extract/translate messages one by one. This usually happens in batches. When you finalize your work or PR, run [`extract`](/docs/ref/cli.md#extract) to generate latest message catalogs and before building the app for production, run [`compile`](/docs/ref/cli.md#compile). + +For more info about CLI, checkout the [CLI tutorial](/docs/tutorials/cli.md). + +## Formatting + +Let's move on to another paragraph in our project. This paragraph has some variables, some HTML and components inside: + +```jsx +

+ See all unread messages{" or "} + mark them as read. +

+``` + +Although it looks complex, there's really nothing special here. Just wrap the content of the paragraph in [`Trans`](/docs/ref/macro.md#trans) and let the macro do the magic: + +```jsx +

+ + See all unread messages{" or "} + mark them as read. + +

+``` + +Spooky, right? Let's see how this message actually looks in the message catalog. Run [`extract`](/docs/ref/cli.md#extract) command and take a look at the message: + +``` jsx +See all <0>unread messages or <1>mark them as read. +``` + +You may notice that components and html tags are replaced with indexed tags (*<0>*, *<1>*). This is a little extension to the ICU MessageFormat which allows rich-text formatting inside translations. Components and their props remain in the source code and don't scare our translators. The tags in the extracted message won't scare our translators either: they are used to seeing tags and their tools support them. Also, in case we change a `className`, we don't need to update our message catalogs. How cool is that? + +### JSX to MessageFormat transformations + +It may look a bit *hackish* at first sight, but these transformations are actually very easy, intuitive and feel very *Reactish*. We don't have to think about the MessageFormat, because it's created by the library. We write our components in the same way as we're used to and simply wrap text in the [`Trans`](/docs/ref/macro.md#trans) macro. + +Let's see some examples with MessageFormat equivalents: + +```jsx +// Expressions +

Hello {name}

+// Hello {name} +``` + +Any expressions are allowed, not just simple variables. The only difference is, only the variable name will be included in the extracted message: + +- Simple variable -> named argument: + + ```jsx +

Hello {name}

+ // Hello {name} + ``` + +- Any expression -> positional argument: + + ```jsx +

Hello {user.name}

+ // Hello {0} + ``` + +- Object, arrays, function calls -> positional argument: + + ```jsx +

The random number is {Math.rand()}

+ // The random number is {0} + ``` + +- Components might get tricky, but like we saw, it's really easy: + + ```jsx + Read more. + // Read <0>more. + ``` + + ```jsx + + Dear Watson,
+ it's not exactly what I had in my mind. +
+ // Dear Watson,<0/>it's not exactly what I had in my mind. + ``` + +Obviously, you can also shoot yourself in the foot. Some expressions are *valid* and won't throw any error, yet it doesn't make any sense to write: + +```jsx +// Oh, seriously? + + {isOpen && } + +``` + +If in doubt, imagine how the final message should look like. + +### Message ID + +At this point we're going to explain what message ID is and how to set it manually. + +Translators work with the *message catalogs* we saw above. No matter what format we use (gettext, xliff, json), it's just a mapping of a message ID to the translation. + +Here's an example of a simple message catalog in **Czech** language: + +| Message ID | Translation | +|------------|-------------| +| Monday | Pondělí | +| Tuesday | Úterý | +| Wednesday | Středa | + +... and the same catalog in **French** language: + +| Message ID | Translation | +|------------|-------------| +| Monday | Lundi | +| Tuesday | Mardi | +| Wednesday | Mercredi | + +The message ID is *what all catalogs have in common* – Lundi and Pondělí represent the same message in different languages. It's also the same as the `id` prop in [`Trans`](/docs/ref/macro.md#trans) macro. + +There are two approaches to how a message ID can be created: + +1. Using the source language (e.g. `Monday` from English, as in example above) +2. Using a custom id (e.g. `weekday.monday`) + +Both approaches have their pros and cons and it's not in the scope of this tutorial to compare them. + +By default, [LinguiJS](https://github.com/lingui/js-lingui) generates message ID from the content of [`Trans`](/docs/ref/macro.md#trans) macro, which means it uses the source language. However, we can easily override it by setting the `id` prop manually: + +```jsx +

Message Inbox

+``` + +This will generate: + +```jsx +

+``` + +In our message catalog, we'll see `inbox.title` as message ID, but we also get `Message Inbox` as default translation for English. + +For the rest of this tutorial, we'll use auto-generated message IDs to keep it simple. + +## Plurals + +Let's move on and add i18n to another text in our component: + +```jsx +

+ { + messagesCount === 1 + ? `There's ${messagesCount} message in your inbox.` + : `There are ${messagesCount} messages in your inbox.` + } +

+``` + +This message is a bit special, because it depends on the value of the `messagesCount` variable. Most languages use different forms of words when describing quantities - this is called [pluralization](https://en.wikipedia.org/wiki/Plural). + +What's tricky is that different languages use different number of plural forms. For example, English has only two forms - singular and plural - as we can see in the example above. However, Czech language has three plural forms. Some languages have up to 6 plural forms and some don't have plurals at all! + +:::tip +Plural forms for all languages can be found in the [CLDR repository](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html). +::: + +Let's load plural data into our app: + +```jsx title="src/index.js" {6,11-14} +import React from 'react' +import { render } from 'react-dom' + +import { i18n } from '@lingui/core' +import { I18nProvider } from '@lingui/react' +import { en, cs } from 'make-plural/plurals' +import { messages as enMessages } from './locales/en/messages' +import { messages as csMessages } from './locales/cs/messages' +import Inbox from './Inbox' + +i18n.loadLocaleData({ + en: { plurals: en }, + cs: { plurals: cs }, +}) + +i18n.load({ + en: enMessages, + cs: csMessages, +}) + +i18n.activate('cs') + +const App = () => ( + + + +) + +render(, document.getElementById('root')) +``` + +### English plural rules + +How do we know which plural form we should use? It's very simple: we, as developers, only need to know plural forms of the language we use in our source. Our component is written in English, so looking at [English plural rules](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#en) we'll need just two forms: + +`one` + +> Singular form + +`other` + +> Plural form + +We don't need to select these forms manually. We'll use [`Plural`](/docs/ref/macro.md#plural-1) component, which takes a `value` prop and based on the active language, selects the right plural form: + +``` jsx +import { Trans, Plural } from '@lingui/macro' + +

+ +

+``` + +This component will render `There's 1 message in your inbox` when `messageCount = 1` and `There are # messages in your inbox` for any other values of `messageCount`. `#` is a placeholder, which is replaced with `value`. + +Cool! Curious how this component is transformed under the hood and how the message looks in MessageFormat syntax? Run [`extract`](/docs/ref/cli.md#extract) command and find out by yourself: + +```icu-message-format +{messagesCount, plural, + one {There's # message in your inbox} + other {There are # messages in your inbox}} +``` + +In the catalog, you'll see the message in one line. Here we wrapped it to make it more readable. + +The [`Plural`](/docs/ref/macro.md#plural-1) is gone and replaced with [`Trans`](/docs/ref/react.md#trans) again! The sole purpose of [`Plural`](/docs/ref/macro.md#plural-1) is to generate proper syntax in message. + +Things are getting a bit more complicated, but i18n is a complex process. At least we don't have to write this message manually! + +### Beware of zeroes! + +Just a short detour, because it's a common misunderstanding. + +You may wonder why the following code doesn't work as expected: + +```jsx + +``` + +This component will render `There are 0 messages in your inbox` for `messagesCount = 0`. Why so? Because English doesn't have `zero` [plural form](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#en). + +Looking at [English plural rules](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#en), it's: + +| N | Form | +|-----|-----------------------| +| 0 | other | +| 1 | one | +| n | other (anything else) | + +However, decimal numbers (even `1.0`) use `other` form every time: + +``` default +There are 0.0 messages in your inbox. +``` + +Aren't languages beautiful? + +### Exact forms + +Alright, back to our example. What if we really want to render `There are no messages` for `messagesCount = 0`? Exact forms to the rescue! + +```jsx + +``` + +What's that `_0`? MessageFormat allows exact forms, like `=0`. However, React props can't start with `=` and can't be numbers either, so we need to write `_N` instead of `=0`. + +It works with any number, so we can go wild and customize it this way: + +```jsx + +``` + +... and so on. Exact matches always take precedence before plural forms. + +### Variables and components + +Let's go back to our original pluralized message: + +```jsx +

+ +

+``` + +What if we want to use variables or components inside messages? Easy! Either wrap messages in [`Trans`](/docs/ref/macro.md#trans) macro or use template literals (suppose we have a variable `name`): + +```jsx +

+ There are # messages in your inbox, {name}} + /> +

+``` + +We can use nested macros, components, variables, expressions, really anything. + +This gives us enough flexibility for all usecases. + +### Custom message ID + +Let's finish this with a short example of plurals with custom ID. We can pass an `id` prop to [`Plural`](/docs/ref/macro.md#plural-1) as we would to [`Trans`](/docs/ref/macro.md#trans): + +```jsx +

+ +

+``` + +## Formats + +The last message in our component is again a bit specific: + +```jsx +
+ Last login on {lastLogin.toLocaleDateString()}. +
+``` + +`lastLogin` is a date object, and we need to format it properly. Dates are formatted differently in different languages, but we don't have to do this manually. The heavy lifting is done by the [Intl object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl), we'll just use [`i18n.date()`](/docs/ref/core.md#i18n.date) function. The `i18n` object can be accessed by [`useLingui`](/docs/ref/react.md#uselingui) hook: + +```jsx title="src/Inbox.js" +import { useLingui } from '@lingui/react' + +export default function Inbox() { + const { i18n } = useLingui() + // ... + + return ( +
+ {/* ... */} +
+ + Last login on {i18n.date(lastLogin)}. + +
+
+ ) +} +``` + +This will format the date using the conventional format for the active language. + +## Review + +After all modifications, the final component with i18n looks like this: + +``` jsx title="src/Inbox.js" +import React from 'react' +import { Trans, Plural } from '@lingui/macro' +import { useLingui } from '@lingui/react' + +export default function Inbox() { + const { i18n } = useLingui() + const messages = [{}, {}] + const messagesCount = messages.length + const lastLogin = new Date() + const markAsRead = () => { alert('Marked as read.') } + + return ( +
+

Message Inbox

+ +

+ + See all unread messages{" or "} + mark them as read. + +

+ +

+ +

+ +
+ + Last login on {i18n.date(lastLogin)}. + +
+
+ ) +} +``` + +That's all for this tutorial! Checkout the reference documentation or various guides in the documentation for more info and happy internationalizing! + +## Further reading + +- [Common i18n patterns in React](/docs/tutorials/react-patterns.md) +- [`@lingui/react` reference documentation](/docs/ref/react.md) +- [`@lingui/cli` reference documentation](/docs/ref/cli.md) +- [Pluralization Guide](/docs/guides/plurals.md) diff --git a/website/docs/tutorials/setup-cra.md b/website/docs/tutorials/setup-cra.md new file mode 100644 index 000000000..71f0578b7 --- /dev/null +++ b/website/docs/tutorials/setup-cra.md @@ -0,0 +1,75 @@ +# Setup with Create React App + +[Create React App](https://github.com/facebook/create-react-app) is a framework for writing React apps with no build configuration. This guide suppose you use Create React App 2.0 (the default version). + +## Install + +1. Follow [Create React App](https://github.com/facebook/create-react-app) documentation for more info. Bootstrap your project with following commands: + + ```bash + npx create-react-app my-app + cd my-app + ``` + +2. Install `@lingui/cli`, `@lingui/macro` and Babel core packages as a development dependencies and `@lingui/react` as a runtime dependency. + + ```bash npm2yarn + npm install --save-dev @lingui/cli @lingui/macro + npm install --save @lingui/react + ``` + + In case you get errors with `import/no-extraneous-dependencies` eslint rule feel free to add the dependencies as non-dev + + ```bash npm2yarn + npm install --save-dev @lingui/cli + npm install --save @lingui/macro @lingui/react + ``` + +3. Create `.linguirc` file with LinguiJS configuration in root of your project (next to `package.json`): + + ``` json title=".linguirc" + { + "locales": ["en", "cs", "fr"], + "sourceLocale": "en", + "catalogs": [{ + "path": "src/locales/{locale}/messages", + "include": ["src"] + }], + "format": "po" + } + ``` + + This configuration will extract messages from source files inside `src` directory and write them into message catalogs in `src/locales` (English catalog would be in e.g: `src/locales/en/messages.po`). Finally, PO format is recommended. + See [`format`](/docs/ref/catalog-formats.md) documentation for other available formats. + +4. Optionally, add following scripts to your `package.json` for convenience: + + ```json title="package.json" + { + "scripts": { + "extract": "lingui extract", + "compile": "lingui compile" + } + } + ``` + +5. Check the installation by running extract command: + + ```bash npm2yarn + npm run extract + ``` + + There should be no error and you can find extracted messages in `src/locales`. + +Congratulations! You've successfully set up project with LinguiJS. Now it's good time to follow [React tutorial](/docs/tutorials/react.md) or read about [ICU Message Format](/docs/ref/message-format.md) which is used in messages. + +## Further reading + +Checkout these reference guides for full documentation: + +- [Internationalization of React apps](/docs/tutorials/react.md) +- [Common i18n patterns in React](/docs/tutorials/react-patterns.md) +- [`@lingui/react` reference documentation](/docs/ref/react.md) +- [ICU Message Format](/docs/ref/message-format.md) +- [CLI reference](/docs/ref/cli.md) +- [Configuration reference](/docs/ref/conf.md) diff --git a/website/docs/tutorials/setup-react.md b/website/docs/tutorials/setup-react.md new file mode 100644 index 000000000..910993524 --- /dev/null +++ b/website/docs/tutorials/setup-react.md @@ -0,0 +1,102 @@ +# Setup with React project + +:::info +If you use Create React App, even ejected, follow [LinguiJS and Create React App](/docs/tutorials/setup-cra.md) setup guide. +::: + +This setup guide is for any project which uses React. + +## Install + +1. Install `@lingui/cli`, `@lingui/macro`, *babel-plugin-macros* and Babel core packages as a development dependencies and `@lingui/react` as a runtime dependency. + + ```bash npm2yarn + npm install --save-dev @lingui/cli @babel/core + npm install --save-dev @lingui/macro babel-plugin-macros # required for macros + npm install --save @lingui/react + ``` + + In case you get errors with `import/no-extraneous-dependencies` eslint rule feel free to add the dependencies as non-dev + + ```bash npm2yarn + npm install --save-dev @lingui/cli @babel/core + npm install --save-dev babel-plugin-macros # required for macros + npm install --save @lingui/macro @lingui/react + ``` + +2. Add `macros` plugin to Babel config (e.g: `.babelrc`): + + ``` json + { + "plugins": [ + "macros" + ] + } + ``` + + :::info + If you use any preset, check first if it contains `macros` plugin. These presets already includes `macros` plugin: `react-scripts` + ::: + +3. Create `.linguirc` file with LinguiJS configuration in root of your project (next to `package.json`). Replace `src` with a directory name where you have source files: + + ```json title=".linguirc" + { + "locales": ["en", "cs", "fr"], + "catalogs": [{ + "path": "src/locales/{locale}/messages", + "include": ["src"] + }], + "format": "po" + } + ``` + + PO format is recommended for message catalogs. See [`format`](/docs/ref/catalog-formats.md) documentation for other available formats. + +4. Add following scripts to your `package.json`: + + ```json title="package.json" + { + "scripts": { + "extract": "lingui extract", + "compile": "lingui compile", + } + } + ``` + +5. Check the installation by running: + + ```bash npm2yarn + npm run extract + ``` + + There should be no error and you should see output similar following: + + ```bash npm2yarn + > npm run extract + + Catalog statistics: + ┌──────────┬─────────────┬─────────┐ + │ Language │ Total count │ Missing │ + ├──────────┼─────────────┼─────────┤ + │ cs │ 0 │ 0 │ + │ en │ 0 │ 0 │ + │ fr │ 0 │ 0 │ + └──────────┴─────────────┴─────────┘ + + (use "lingui extract" to update catalogs with new messages) + (use "lingui compile" to compile catalogs for production) + ``` + +Congratulations! You've successfully set up project with LinguiJS. Now it's good time to follow [React tutorial](/docs/tutorials/react.md) or read about [ICU Message Format](/docs/ref/message-format.md) which is used in messages. + +## Further reading + +Checkout these reference guides for full documentation: + +- [Internationalization of React apps](/docs/tutorials/react.md) +- [Common i18n patterns in React](/docs/tutorials/react-patterns.md) +- [`@lingui/react` reference documentation](/docs/ref/react.md) +- [ICU Message Format](/docs/ref/message-format.md) +- [CLI reference](/docs/ref/cli.md) +- [Configuration reference](/docs/ref/conf.md) diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js new file mode 100644 index 000000000..055e74ff5 --- /dev/null +++ b/website/docusaurus.config.js @@ -0,0 +1,131 @@ +const copyright = `Copyright © 2017-2022 Tom Ehrlich, © 2022-${new Date().getFullYear()} Crowdin.`; +const url = process.env['SITE_URL'] || 'https://lingui.dev'; + +/** @type {import('@docusaurus/types').DocusaurusConfig} */ +module.exports = { + title: 'Lingui', + tagline: 'Professional localization framework for global products', + url: url, + baseUrl: '/', + onBrokenLinks: 'throw', + favicon: 'img/favicon.ico', + organizationName: 'lingui', + themeConfig: { + colorMode: { + disableSwitch: false, + respectPrefersColorScheme: true, + }, + navbar: { + title: '', + logo: { + alt: 'Lingui', + src: 'img/logo-small.svg', + }, + items: [ + { + to: '/introduction', + activeBasePath: '/introduction', + label: 'Docs', + position: 'left', + }, + { + href: 'https://github.com/lingui/js-lingui', + position: 'right', + className: 'header-github-link', + 'aria-label': 'GitHub repository', + }, + ], + }, + footer: { + style: 'dark', + copyright, + links: [ + { + title: 'Docs', + items: [ + { + label: 'Introduction', + to: '/introduction/', + }, + { + label: 'CLI Reference', + to: '/ref/cli', + }, + { + label: 'Configuration', + to: '/ref/conf', + }, + ], + }, + { + title: 'Community', + items: [ + { + label: 'Discord', + href: 'https://discord.gg/gFWwAYnMtA', + }, + { + label: 'Stack Overflow', + href: 'https://stackoverflow.com/questions/tagged/linguijs', + }, + { + label: 'Discussions', + href: 'https://github.com/lingui/js-lingui/discussions', + }, + ], + }, + { + title: 'More', + items: [ + { + label: 'GitHub', + href: 'https://github.com/lingui/js-lingui', + }, + { + label: 'Twitter', + href: 'https://twitter.com/LinguiJS', + }, + { + label: 'ICU Playground', + href: 'https://format-message.github.io/icu-message-format-for-translators/editor.html', + }, + ], + }, + ], + }, + algolia: { + appId: 'JJFVB18YWS', + apiKey: '50e12ed6fd44188e9abd4e0e9d2cb935', + indexName: 'lingui', + }, + prism: { + theme: require('prism-react-renderer/themes/github'), + darkTheme: require('prism-react-renderer/themes/palenight'), + additionalLanguages: ['bash', 'docker', 'gettext', 'icu-message-format', 'ignore'], + }, + }, + presets: [ + [ + '@docusaurus/preset-classic', + { + docs: { + sidebarPath: require.resolve('./sidebars.ts'), + sidebarCollapsible: false, + breadcrumbs: false, + routeBasePath: '/', + editUrl: 'https://github.com/lingui/js-lingui/tree/main/website', + remarkPlugins: [[require('@docusaurus/remark-plugin-npm2yarn'), { sync: true }]], + }, + sitemap: { + changefreq: 'weekly', + priority: 0.5, + filename: 'sitemap.xml', + }, + theme: { + customCss: require.resolve('./src/css/custom.scss'), + }, + }, + ], + ], + plugins: ['docusaurus-plugin-sass'], +}; diff --git a/website/linkcheck-ignore.txt b/website/linkcheck-ignore.txt new file mode 100644 index 000000000..a5ace2765 --- /dev/null +++ b/website/linkcheck-ignore.txt @@ -0,0 +1,2 @@ +http://localhost:\d{4} +https://.*.algolia.net diff --git a/website/package.json b/website/package.json new file mode 100644 index 000000000..8c09cc540 --- /dev/null +++ b/website/package.json @@ -0,0 +1,71 @@ +{ + "name": "js-lingui-website", + "version": "0.0.0", + "private": true, + "scripts": { + "docusaurus": "docusaurus", + "start": "docusaurus start", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "clear": "docusaurus clear", + "serve": "docusaurus serve", + "lint": "eslint src *.js *.ts && remark . --ext md,mdx --quiet --frail --rc-path .remarkrc.mjs && editorconfig-checker", + "lintFix": "eslint --fix src *.js *.ts", + "checkFormat": "prettier --check .", + "fixFormat": "prettier --write ." + }, + "dependencies": { + "@docusaurus/core": "2.2.0", + "@docusaurus/preset-classic": "2.2.0", + "@docusaurus/remark-plugin-npm2yarn": "^2.2.0", + "@mdx-js/react": "1.6.22", + "clsx": "1.2.1", + "docusaurus-plugin-sass": "^0.2.3", + "react": "17.0.0", + "react-dom": "17.0.0", + "sass": "^1.57.1" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "devDependencies": { + "@docusaurus/eslint-plugin": "2.2.0", + "@docusaurus/module-type-aliases": "2.2.0", + "@docusaurus/utils": "2.2.0", + "@tsconfig/docusaurus": "1.0.6", + "@types/react": "17.0.0", + "@types/react-helmet": "6.1.6", + "@types/react-router-dom": "5.3.3", + "@typescript-eslint/eslint-plugin": "5.48.1", + "@typescript-eslint/parser": "5.48.1", + "editorconfig-checker": "4.0.0", + "eslint": "8.31.0", + "eslint-plugin-react": "7.32.0", + "eslint-plugin-react-hooks": "4.6.0", + "prettier": "2.8.2", + "remark-cli": "11.0.0", + "remark-heading-id": "^1.0.0", + "remark-lint-match-punctuation": "0.2.1", + "remark-lint-no-duplicate-headings-in-section": "3.1.1", + "remark-lint-no-tabs": "3.1.1", + "remark-lint-no-trailing-spaces": "2.0.1", + "remark-preset-lint-consistent": "5.1.1", + "remark-preset-lint-markdown-style-guide": "5.1.2", + "remark-preset-lint-recommended": "6.1.2", + "remark-retext": "5.0.1", + "remark-validate-links": "12.1.0", + "retext-diacritics": "4.2.0", + "retext-english": "4.1.0", + "retext-indefinite-article": "4.2.0", + "retext-redundant-acronyms": "4.3.0", + "retext-repeated-words": "4.2.0", + "retext-sentence-spacing": "5.2.0", + "retext-syntax-mentions": "3.1.0", + "retext-syntax-urls": "3.1.2", + "typescript": "4.9.4", + "typescript-plugin-css-modules": "^4.1.1", + "unified": "10.1.2" + } +} diff --git a/website/sidebars.ts b/website/sidebars.ts new file mode 100644 index 000000000..b59b2f6fa --- /dev/null +++ b/website/sidebars.ts @@ -0,0 +1,175 @@ +const sidebar = [ + { + type: 'doc', + label: 'Introduction', + id: 'introduction', + }, + { + type: 'category', + label: 'Installation', + items: [ + { + type: 'doc', + label: 'Create React App', + id: 'tutorials/setup-cra', + }, + { + type: 'doc', + label: 'React project', + id: 'tutorials/setup-react', + }, + ], + }, + { + type: 'category', + label: 'Tutorials', + items: [ + { + type: 'doc', + label: 'React', + id: 'tutorials/react', + }, + { + type: 'doc', + label: 'React - common patterns', + id: 'tutorials/react-patterns', + }, + { + type: 'doc', + label: 'React Native', + id: 'tutorials/react-native', + }, + { + type: 'doc', + label: 'JavaScript', + id: 'tutorials/javascript', + }, + { + type: 'doc', + label: 'CLI', + id: 'tutorials/cli', + }, + ], + }, + { + type: 'category', + label: 'Guides', + items: [ + { + type: 'doc', + label: 'Testing', + id: 'guides/testing', + }, + { + type: 'doc', + label: 'TypeScript', + id: 'guides/typescript', + }, + { + type: 'doc', + label: 'Excluding build files', + id: 'guides/excluding-build-files', + }, + { + type: 'doc', + label: 'Dynamic loading', + id: 'guides/dynamic-loading-catalogs', + }, + { + type: 'doc', + label: 'Optimized components', + id: 'guides/optimized-components', + }, + { + type: 'doc', + label: 'Pluralization', + id: 'guides/plurals', + }, + { + type: 'doc', + label: 'Monorepo', + id: 'guides/monorepo', + }, + { + type: 'doc', + label: 'Pseudolocalization', + id: 'guides/pseudolocalization', + }, + ], + }, + { + type: 'category', + label: 'API Reference', + items: [ + { + type: 'doc', + label: '@lingui/core', + id: 'ref/core', + }, + { + type: 'doc', + label: '@lingui/react', + id: 'ref/react', + }, + { + type: 'doc', + label: '@lingui/macro', + id: 'ref/macro', + }, + { + type: 'doc', + label: '@lingui/cli', + id: 'ref/cli', + }, + { + type: 'doc', + label: '@lingui/locale-detector', + id: 'ref/locale-detector', + }, + { + type: 'doc', + label: '@lingui/loader', + id: 'ref/loader', + }, + { + type: 'doc', + label: '@lingui/snowpack-plugin', + id: 'ref/snowpack-plugin', + }, + { + type: 'doc', + label: 'Lingui Configuration', + id: 'ref/conf', + }, + { + type: 'doc', + label: 'Catalog formats', + id: 'ref/catalog-formats', + }, + { + type: 'doc', + label: 'ICU MessageFormat', + id: 'ref/message-format', + }, + ], + }, + { + type: 'category', + label: 'Sync & Collaboration Tools', + items: ['tools/introduction', 'tools/crowdin', 'tools/translation-io'], + }, + { + type: 'category', + label: 'Resources', + items: ['misc/community', 'misc/resources', 'misc/showroom', 'misc/react-intl', 'misc/tooling'], + }, + { + type: 'category', + label: 'Releases', + items: ['releases/migration-3'], + }, +]; + +module.exports = { + sidebar, +}; diff --git a/website/src/components/Button.tsx b/website/src/components/Button.tsx new file mode 100644 index 000000000..a8bf3f281 --- /dev/null +++ b/website/src/components/Button.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import Link from '@docusaurus/Link'; + +type ButtonProps = { + href: string; + children: string; + isOutline?: boolean; +}; + +const Button = (props: ButtonProps): React.ReactElement => { + return ( + + {props.children} + + ); +}; + +export default Button; diff --git a/website/src/components/Features.module.scss b/website/src/components/Features.module.scss new file mode 100644 index 000000000..34a8fa405 --- /dev/null +++ b/website/src/components/Features.module.scss @@ -0,0 +1,101 @@ +.features { + &--wrap { + display: grid; + gap: 1.5rem; + row-gap: 0; + grid-template-columns: repeat(1, 1fr); + + @media screen and (min-width: 581px) { + grid-template-columns: repeat(2, 1fr); + } + + @media only screen and (min-width: 851px) { + grid-template-columns: repeat(3, 1fr); + } + } + + &--left-img { + position: relative; + grid-row: 2; + grid-column: 1 / -1; + height: 0; + z-index: 0; + + img { + bottom: -40px; + position: absolute; + left: -150px; + width: 900px; + height: 680px; + } + } + + &--right-img { + position: relative; + grid-row: 4; + grid-column: 1 / -1; + height: 0; + z-index: 0; + + img { + top: -70px; + position: absolute; + right: -150px; + width: 900px; + height: 680px; + } + } +} + +.linkFeatures { + margin-top: 2rem; + text-align: center; +} + +.featureCard { + background-color: var(--lingui-card-bg); + backdrop-filter: blur(10px); + border-radius: 15px; + padding: 1.5rem; + display: grid; + z-index: 1; + position: relative; + border: 1px solid; + border-color: var(--lingui-card-border-color); + margin-bottom: 1.5rem; + grid-template-columns: 1fr; + grid-template-rows: 64px 1fr; + + &CellWide { + @media screen and (min-width: 851px) { + grid-column-start: auto; + grid-column-end: span 2; + } + } + + img { + width: 4rem; + } + + &Content { + margin-top: 1rem; + } + + h3 { + color: var(--ifm-heading-color); + font-weight: normal; + font-size: 1.375rem; + } + + p { + color: var(--p-color); + font-size: var(--font-size); + } +} + +@media only screen and (max-width: 600px) { + .featureCard { + text-align: left; + column-gap: 2rem; + } +} diff --git a/website/src/components/Features.tsx b/website/src/components/Features.tsx new file mode 100644 index 000000000..3d17d69b5 --- /dev/null +++ b/website/src/components/Features.tsx @@ -0,0 +1,197 @@ +import React from 'react'; +import { useBaseUrlUtils } from '@docusaurus/useBaseUrl'; + +import styles from './Features.module.scss'; +import Button from './Button'; +import clsx from 'clsx'; + +interface FeatureDetails { + title: string; + description: JSX.Element; + image: string; + additionalClass: string; +} + +const FEATURES: FeatureDetails[] = [ + { + title: 'Universal', + description: ( +

+ Use it everywhere. @lingui/core provides the essential intl functionality which works in any + JavaScript project, while @lingui/react offers components for leveraging React rendering. +

+ ), + image: 'universal.svg', + additionalClass: '', + }, + { + title: 'Powerful Tooling', + description: ( +

+ Manage the whole intl workflow using Lingui CLI. It extracts messages from source code, validates messages from + translators and checks that all messages are translated before shipping to production. +

+ ), + image: 'tooling.png', + additionalClass: '', + }, + { + title: 'Full Rich-Text Support', + description: ( +

+ Use React components inside localized messages without any limitations. Writing rich-text messages is as easy as + writing JSX. +

+ ), + image: 'rich-text.svg', + additionalClass: '', + }, + { + title: 'Headache-Free Professional Localization', + description: ( +
+
+ Candidate knows 1 language, but{' '} + + Candidate knows 10 languages + + . +
+

+ You don't have to know how many plurals the language has. Create a product in one language, and deliver a + perfect translation to users. Lingui follows Unicode ICU standards to handle plurals, genders, and selects. +

+
+ ), + image: 'clean-and-readable.png', + additionalClass: styles.featureCardCellWide, + }, + { + title: 'Suitable for All Localization Platforms', + description: ( +

+ Integrate Lingui into your existing workflow. It supports message keys as well as auto-generated messages. + Translations are stored in JSON or standard PO file, which is supported in almost all translation tools. +

+ ), + image: 'all-platforms.svg', + additionalClass: '', + }, + { + title: 'Verified by Thousands of People', + description: ( +

+ Lingui has been used and tested by thousands of satisfied users and has been proven to provide accurate and + efficient i18n and l10n results. Join the thousands of satisfied customers. +

+ ), + image: 'verified.svg', + additionalClass: '', + }, + { + title: 'Battle-Proven & Future Proof', + description: ( +

+ During the last 7 years, we've seen a lot of localization projects and developed a tool to handle them all. +
+ If your team needs to edit source texts without developer involvement, or you want the ability to deliver the + most recent translations directly to your customers – we've got you covered. +

+ ), + image: 'time.svg', + additionalClass: styles.featureCardCellWide, + }, + { + title: 'Fully Fledged', + description: ( +

+ Lingui is a general-purpose framework with bindings for React. Use it on a server in Node.js or Vanilla + JavaScript. +
A set of optional modules would implement lazy loading of language packs, user locale detection, and more. +

+ ), + image: 'fledged.svg', + additionalClass: styles.featureCardCellWide, + }, + { + title: 'Free and Fully Supported', + description: ( +

+ With Lingui, you have access to a powerful i18n tool at no cost and our team is always available to assist you + with any questions. +

+ ), + image: 'free.svg', + additionalClass: '', + }, +]; + +const FeatureCard = ({ title, description, image, additionalClass }: FeatureDetails): React.ReactElement => ( +
+ Feature Logo +
+

{title}

+
{description}
+
+
+); + +const Features = (): React.ReactElement => { + const { withBaseUrl } = useBaseUrlUtils(); + + return ( +
+
+
+
+

Why Choose Lingui for Your Localization Projects?

+
+
+
+ {FEATURES.slice(0, 4).map((feature: FeatureDetails, idx) => ( + + ))} +
+ +
+ {FEATURES.slice(4, 6).map((feature: FeatureDetails, idx) => ( + + ))} +
+ +
+ {FEATURES.slice(6).map((feature: FeatureDetails, idx) => ( + + ))} +
+
+
+
+
+ +
+
+ ); +}; + +export default Features; diff --git a/website/src/components/Header.module.scss b/website/src/components/Header.module.scss new file mode 100644 index 000000000..15d6b1ae7 --- /dev/null +++ b/website/src/components/Header.module.scss @@ -0,0 +1,73 @@ +.heroBanner { + text-align: center; + position: relative; + + &Holder { + padding: 0; + overflow: hidden; + background: var(--ifm-hero-background-bg); + border-radius: 0 0 var(--lingui-card-raduis) var(--lingui-card-raduis); + display: flex; + flex: 1; + position: relative; + + @media screen and (max-width: 850px) { + border-radius: 0 0 var(--lingui-card-raduis-sm) var(--lingui-card-raduis-sm); + } + + &:before, + &:after { + content: ''; + display: block; + position: absolute; + width: 562px; + height: 100%; + background-repeat: no-repeat; + background-size: cover; + top: 0; + z-index: 0; + + @media screen and (max-width: 850px) { + opacity: 0.5; + } + } + + &:before { + left: 0; + background-image: url('/img/header/left-bg.svg'); + } + + &:after { + right: 0; + background-image: url('/img/header/right-bg.svg'); + } + } + + &Container { + position: relative; + z-index: 1; + padding-bottom: 4rem; + padding-top: 4rem; + + @media screen and (max-width: 850px) { + backdrop-filter: blur(10px); + } + } +} + +.heroTitle { + font-size: 3rem; + font-weight: 700; +} + +.heroBannerLogo { + max-width: 170px; + max-height: 170px; + margin-left: -10px; +} + +.heroButtons { + display: flex; + justify-content: center; + align-items: center; +} diff --git a/website/src/components/Header.tsx b/website/src/components/Header.tsx new file mode 100644 index 000000000..f9503953d --- /dev/null +++ b/website/src/components/Header.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import clsx from 'clsx'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Button from './Button'; + +import styles from './Header.module.scss'; + +const Header = (): React.ReactElement => { + return ( +
+
+
+
+
+ Lingui +

Internationalization Framework for Global Products

+

+ JavaScript library for internalization (i18n) of JavaScript projects, including React, Vue, Node.js, and + Angular. +

+ +
+ + +
+