Repository: lingui/js-lingui (ts)

Issue:
@lingui/loader does not work with pseudo locale
**Describe the bug**

@lingui/loader does not work with pseudo locale

**To Reproduce**

.linguirc
```ts
{
   "locales": ["en", "nl"],
   "sourceLocale": "en",
   "catalogs": [{
      "path": "<rootDir>/locales/{locale}/messages",
      "include": ["<rootDir>/**"],
      "exclude": ["**/node_modules/**"]
   }],
   "pseudoLocale": "en-fo",
   "rootDir": ".",
   "format": "po"
}
```

```ts
const buildDynamicActivate = (i18n: any) => async (locale: string) => {
  const { messages } = await import(`@lingui/loader!../../locales/${locale}/messages.po`);
  i18n.load(locale, messages);
  i18n.activate(locale);
};

buildDynamicActivate(i18n)('en-fo'); // load pseudo translations
```

**Expected behavior**

Pseudo locale is loaded

**Additional context**

Add any other context about the problem here.

This is related to #830 and #836

- jsLingui version 3.0.3


Pull request description:
docs: create pseudolocalization guide
I have moved psedulocalization to guides from cli tutorials

This closes #838 as the documentation now specifies `fallbackLocales`

Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0028a2ed..663006e7 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -65,9 +65,15 @@ jobs:
           actor: 'lingui-bot'
           token: '${{ secrets.GH_TOKEN }}'
 
-      - name: Release packages
+      - name: Versioning packages
         env:
           GH_TOKEN: ${{secrets.GH_TOKEN}}
-        run: |
-          yarn version:${{github.event.inputs.npmTag}}
-          yarn release:${{github.event.inputs.npmTag}}
+        run: yarn version:${{github.event.inputs.npmTag}}
+
+      - name: Packaging packages with newer version
+        run: yarn release:build
+
+      - name: Publishing packages
+        env:
+          GH_TOKEN: ${{secrets.GH_TOKEN}}
+        run: yarn release:${{github.event.inputs.npmTag}}
diff --git a/docs/guides/pseudolocalization.rst b/docs/guides/pseudolocalization.rst
new file mode 100644
index 00000000..260af04f
--- /dev/null
+++ b/docs/guides/pseudolocalization.rst
@@ -0,0 +1,57 @@
+==================
+Pseudolocalization
+==================
+
+There is built in support for `pseudolocalization <https://en.wikipedia.org/wiki/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 </ref/conf>`::
+
+   {
+      "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/index.rst b/docs/index.rst
index 0c3c928d..f421750a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -100,6 +100,7 @@ Documentation contents
    Optimized components <guides/optimized-components>
    How plurals work <guides/plurals>
    Lingui within monorepo <guides/monorepo>
+   Pseudolocalization <guides/pseudolocalization>
 
 .. toctree::
    :maxdepth: 1
diff --git a/docs/ref/react.rst b/docs/ref/react.rst
index 788315cb..2c36d4b0 100644
--- a/docs/ref/react.rst
+++ b/docs/ref/react.rst
@@ -173,7 +173,7 @@ top-level application component. However, if the ``locale`` is stored in a
    import React from 'react';
    import { I18nProvider } from '@lingui/react';
    import { i18n } from '@lingui/core';
-   import { messages as MessagesEn } from './locales/en/messages.js';
+   import { messages as messagesEn } from './locales/en/messages.js';
 
    i18n.load({
       en: messagesEn,
diff --git a/docs/tutorials/cli.rst b/docs/tutorials/cli.rst
index 974d0525..db836267 100644
--- a/docs/tutorials/cli.rst
+++ b/docs/tutorials/cli.rst
@@ -6,7 +6,7 @@ Working with LinguiJS CLI
 *************************
 
 ``@lingui/cli`` provides the ``lingui`` command for extracting, merging and
-compiling message catalogs. Follow `setup instructions <../ref/cli>`_ to install required
+compiling message catalogs. Follow `setup instructions <../ref/cli.html>`_ to install required
 packages.
 
 .. note::
@@ -182,49 +182,6 @@ If you use natural language for message IDs (that's the default),
 set :conf:`sourceLocale`. You shouldn't use this config if you're using custom
 IDs (e.g: ``Component.title``).
 
-Pseudolocalization
-==================
-
-There is built in support for `pseudolocalization <https://en.wikipedia.org/wiki/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ţ ĩś ƥśēũďōĺōćàĺĩźēď
-
-To setup pseudolocalization add :conf:`pseudoLocale` in ``package.json``::
-
-   {
-      "lingui": {
-         "pseudoLocale": "pseudo-LOCALE"
-      }
-   }
-
-:conf:`pseudoLocale` option can be any string 
-examples: :conf:`en-PL`, :conf:`pseudo-LOCALE`, :conf:`pseudolocalization` or :conf:`en-UK`
-
-PseudoLocale string have to be in :conf:`locales` config as well. Otherwise no folder is going to be created.
-Pseudolocalized text is created on  ``yarn compile`` command.
-The pseudolocalization is automatically created on ``yarn extract`` from messages in order specified in :ref:`Preparing catalogs for production`.
-
-How to switch your 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 (any string) - https://chrome.google.com/webstore/detail/quick-language-switcher/pmjbhfmaphnpbehdanbjphdcniaelfie
-b) Without extension - 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 - about:preferences#general > Language
-
 Catalogs in VCS and CI
 ======================
 
diff --git a/docs/tutorials/setup-react.rst b/docs/tutorials/setup-react.rst
index 5bee2d59..12f45ad0 100644
--- a/docs/tutorials/setup-react.rst
+++ b/docs/tutorials/setup-react.rst
@@ -62,6 +62,7 @@ Install
    .. code-block:: json
 
       {
+         "locales": ["en", "cs"],
          "localeDir": "src/locales/",
          "srcPathDirs": ["src/"],
          "format": "po"
@@ -76,24 +77,12 @@ Install
 
       {
          "scripts": {
-            "add-locale": "lingui add-locale",
             "extract": "lingui extract",
             "compile": "lingui compile",
          }
       }
 
-5. Run ``npm run add-locale`` (or ``yarn add-locale``) with
-   `locale codes <https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry>`_
-   you would like to use in your app:
-
-   .. code-block:: shell
-
-      npm run add-locale en es fr  # Add English, Spanish and French locale
-
-      # or using Yarn
-      yarn add-locale en es fr
-
-6. Check the installation by running ``npm run extract`` (or ``yarn extract``):
+5. Check the installation by running ``npm run extract`` (or ``yarn extract``):
 
    .. code-block:: shell
 
@@ -117,7 +106,6 @@ Install
       │ fr       │     0       │   0     │
       └──────────┴─────────────┴─────────┘
 
-      (use "lingui add-locale <language>" to add more locales)
       (use "lingui extract" to update catalogs with new messages)
       (use "lingui compile" to compile catalogs for production)
 
diff --git a/package.json b/package.json
index 2f9f8416..9e178721 100644
--- a/package.json
+++ b/package.json
@@ -20,8 +20,8 @@
     "release:integration": "node ./scripts/integration.js",
     "release:build": "node ./scripts/build/",
     "release:test": "node ./scripts/test.js",
-    "version:next": "lerna version --no-private --preid next --create-release github --conventional-commits --conventional-prerelease --yes",
-    "version:latest": "lerna version --no-private --create-release github --conventional-commits --yes",
+    "version:next": "lerna version --force-publish --no-private --preid next --create-release github --conventional-commits --conventional-prerelease --yes",
+    "version:latest": "lerna version --force-publish --no-private --create-release github --conventional-commits --yes",
     "release:latest": "lerna publish from-package --contents build --dist-tag latest --yes",
     "release:next": "lerna publish from-package --contents build --canary --preid next --pre-dist-tag next --yes",
     "build:docs": "cd docs && ./vercel-build.sh"
diff --git a/packages/cli/src/api/catalog.ts b/packages/cli/src/api/catalog.ts
index f316b382..0a17f420 100644
--- a/packages/cli/src/api/catalog.ts
+++ b/packages/cli/src/api/catalog.ts
@@ -252,7 +252,21 @@ export class Catalog {
       console.error(`Message with key ${key} is missing in locale ${locale}`)
     }
 
-    const getTranslation = (locale) => catalogs[locale][key].translation
+    const getTranslation = (locale) => {
+      const configLocales = this.config.locales.join('", "')
+      if (catalogs[locale]) {
+        return catalogs[locale][key].translation
+      }
+
+      console.warn(`
+        Catalog "${locale}" isn't present in locales config parameter
+        Add "${locale}" to your lingui.config.js:
+        {
+          locales: ["${configLocales}", "${locale}"]
+        }
+      `)
+      return null
+    }
 
     const getMultipleFallbacks = (locale) => {
       const fL = fallbackLocales[locale]
diff --git a/packages/conf/src/index.ts b/packages/conf/src/index.ts
index 0692c3de..9c09082b 100644
--- a/packages/conf/src/index.ts
+++ b/packages/conf/src/index.ts
@@ -278,7 +278,7 @@ export function fallbackLanguageMigration(
 
   config.locales.forEach((locale) => {
     const fl = getCldrParentLocale(locale.toLowerCase())
-    if (fl) {
+    if (fl && !config.fallbackLocales[locale]) {
       config.fallbackLocales = {
         ...config.fallbackLocales,
         [locale]: fl


Test patch:
diff --git a/docs/guides/testing.rst b/docs/guides/testing.rst
index 6e944169..e5094c32 100644
--- a/docs/guides/testing.rst
+++ b/docs/guides/testing.rst
@@ -12,27 +12,48 @@ Here is a working example with `react-testing-library`_, using the `wrapper-prop
 .. code-block:: jsx
 
    // index.js
-   import React from 'react'
-   import { render } from '@testing-library/react'
-   import { i18n } from '@lingui/core'
-   import { I18nProvider } from '@lingui/react'
-   import { en } from 'make-plural/plurals';
-   import { messages } from '../your-path-to/en/messages.js'
-   import Inbox from './Inbox'
-
-   i18n.load('en', messages)
-   i18n.loadLocaleData({ en: { plurals: en } });
-   i18n.activate('en')
-   const TestingProvider = ({ children }) => (
-     <I18nProvider i18n={i18n}>
-       {children}
-     </I18nProvider>
-   )
-
-   test('your example-test', () => {
-     render(<Inbox />, { wrapper: TestingProvider });
-     expect(screen.getByRole('button')).toBeInTheDocument()
-   });
+    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) => (
+      <I18nProvider i18n={i18n}>
+        {children}
+      </I18nProvider>
+    )
+
+    test('Content should be translated correctly in English' , () => {
+      act(() => {
+        i18n.activate('en')
+      })
+      const { getByTestId, container } = render(<App />, { 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(<App />, { 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`_
 
diff --git a/packages/conf/src/index.test.ts b/packages/conf/src/index.test.ts
index cbd13c22..2ec6e15b 100644
--- a/packages/conf/src/index.test.ts
+++ b/packages/conf/src/index.test.ts
@@ -173,7 +173,7 @@ describe("@lingui/conf", function () {
         ),
       })
       expect(config.fallbackLocales).toEqual({
-        "en-US": "en",
+        "en-US": ["en"],
         default: "en",
         "es-MX": "es"
       })