diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 166112a5a7d..1ccee149cb5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -678,7 +678,8 @@ embed.go @grafana/grafana-as-code /.github/workflows/ephemeral-instances-pr-opened-closed.yml @grafana/grafana-operator-experience-squad /.github/workflows/create-security-patch-from-security-mirror.yml @grafana/grafana-release-guild /.github/workflows/core-plugins-build-and-release.yml @grafana/plugins-platform-frontend @grafana/plugins-platform-backend -/.github/workflows/i18n-crowdin-fix-files.yml @grafana/grafana-frontend-platform +/.github/workflows/i18n-crowdin-upload.yml @grafana/grafana-frontend-platform +/.github/workflows/i18n-crowdin-download.yml @grafana/grafana-frontend-platform /.github/workflows/feature-toggle-cleanup.yml @tolzhabayev /.github/workflows/scripts/feature-toggle-cleanup/feature-toggle-cleanup.js @tolzhabayev diff --git a/.github/workflows/i18n-crowdin-download.yml b/.github/workflows/i18n-crowdin-download.yml new file mode 100644 index 00000000000..89d8a1c303e --- /dev/null +++ b/.github/workflows/i18n-crowdin-download.yml @@ -0,0 +1,51 @@ +name: Crowdin Download Action + +on: + workflow_dispatch: + schedule: + - cron: "0 * * * *" + +jobs: + download-sources-from-crowdin: + runs-on: ubuntu-latest + + permissions: + contents: write # needed to commit changes into the PR + pull-requests: write # needed to update PR description, labels, etc + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + + - name: Download sources + uses: crowdin/github-action@v1 + with: + upload_sources: false + upload_translations: false + download_sources: false + download_translations: true + localization_branch_name: i18n_crowdin_translations + create_pull_request: true + pull_request_title: 'I18n: Download translations from Crowdin' + pull_request_body: | + :robot: Automatic download of translations from Crowdin. + + Steps for merging: + 1. A quick sanity check of the changes and approve. Things to look out for: + - No changes in the English file. The source of truth is in the main branch, NOT in Crowdin. + - Translations maybe be removed if the English phrase was removed, but there should not be many of these + - Anything else that looks 'funky'. Ask if you're not sure. + 2. Approve & (Auto-)merge. :tada: + + If there's a conflict, close the pull request and **delete the branch**. A GH action will recreate the pull request. + Remember, the longer this pull request is open, the more likely it is that it'll get conflicts. + pull_request_labels: 'area/frontend, area/internationalization, no-changelog, no-backport' + pull_request_reviewers: 'grafana-frontend-platform' + pull_request_base_branch_name: 'main' + base_url: 'https://grafana.api.crowdin.com' + config: 'crowdin.yml' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} diff --git a/.github/workflows/i18n-crowdin-fix-files.yml b/.github/workflows/i18n-crowdin-fix-files.yml deleted file mode 100644 index 5117bd381d6..00000000000 --- a/.github/workflows/i18n-crowdin-fix-files.yml +++ /dev/null @@ -1,67 +0,0 @@ -# When Crowdin creates a pull request from the crowdin-service-branch branch, -# run `yarn i18n:extract` and commit the changed grafana.json files back into the PR -# to reformat crowdin's changes to prevent conflicts with our CI checks. - -name: Fix Crowdin I18n files - -on: - pull_request: - paths: - - 'public/locales/*/grafana.json' - branches: - - main # Only run on pull requests *target* main (will be merged into main) - -jobs: - fix-files: - # Only run on pull requests *from* the crowdin-service-branch branch - if: github.head_ref == 'crowdin-service-branch' - - name: Fix files - runs-on: ubuntu-latest - - - permissions: - contents: write # needed to commit changes back into the PR - pull-requests: write # needed to update PR description - - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - - - uses: actions/setup-node@v4 - with: - node-version: 20.9.0 - cache: 'yarn' - - - name: Install dependencies - run: yarn install - - - name: Extract I18n files - run: yarn i18n:extract - - - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@8756aa072ef5b4a080af5dc8fef36c5d586e521d # v5.0.0 - with: - commit_message: "Github Action: Auto-fix i18n files" - file_pattern: public/locales/*/grafana.json - - - name: Update PR description - uses: devindford/Append_PR_Comment@32dd2619cd96ac8da9907c416c992fe265233ca8 # v1.1.3 - if: ${{ ! contains(github.event.pull_request.body, 'Steps for merging') }} - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - body-update-action: prefix - body-template: | - :robot: Automatic sync of translations from Crowdin. - - Steps for merging: - 1. Wait for the "Github Action: Auto-fix i18n files" commit that may be required for CI to pass. - 2. A quick sanity check of the changes and approve. Things to look out for: - - No changes to the English strings. The source of truth is already in the main branch, NOT Crowdin. - - Translations maybe be removed if the English phrase was removed, but there should not be many of these - - Anything else that looks 'funky'. Ask if you're not sure. - 3. Approve & (Auto-)merge. :tada: - - If there's a conflict, close the pull request and **delete the branch**. Crowdin will recreate the pull request eventually. - Remember, the longer this pull request is open, the more likely it is that it'll get conflicts. diff --git a/.github/workflows/i18n-crowdin-upload.yml b/.github/workflows/i18n-crowdin-upload.yml new file mode 100644 index 00000000000..9dcdd5a6b10 --- /dev/null +++ b/.github/workflows/i18n-crowdin-upload.yml @@ -0,0 +1,29 @@ +name: Crowdin Upload Action + +on: + push: + paths: + - 'public/locales/en-US/grafana.json' + branches: + - main + +jobs: + upload-sources-to-crowdin: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Upload sources + uses: crowdin/github-action@v1 + with: + upload_sources: true + upload_translations: false + download_translations: false + create_pull_request: false + base_url: 'https://grafana.api.crowdin.com' + config: 'crowdin.yml' + env: + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} diff --git a/contribute/internationalization.md b/contribute/internationalization.md index 243db35efe2..8d32a65e3ed 100644 --- a/contribute/internationalization.md +++ b/contribute/internationalization.md @@ -9,7 +9,8 @@ Grafana uses the [i18next](https://www.i18next.com/) framework for managing tran - Use `Go to {{ pageTitle }}` in code to add a translatable phrase - Translations are stored in JSON files in `public/locales/{locale}/grafana.json` - If a particular phrase is not available in the a language then it will fall back to English -- To update phrases in English, edit the default phrase in both the component's source and the [English grafana.json message catalogue](../public/locales/en-US/grafana.json), then run `yarn i18n:extract`. +- To update phrases in English, edit the default phrase in the component's source and then run `yarn i18n:extract`. +- The single source of truth for en-US (fallback language) is in grafana/grafana, the single source of truth for any translated language is Crowdin - To update phrases in any translated language, edit the phrase in Crowdin. Do not edit the `{locale}/grafana.json` ## How to add a new translation phrase @@ -40,7 +41,8 @@ const ErrorMessage = ({ id, message }) => There 2. Upon reload, the default English phrase will appear on the page. -3. Before submitting your PR, run the `yarn i18n:extract` command to extract the messages you added into the `grafana.json` file and make them available for translation. +3. Before submitting your PR, run the `yarn i18n:extract` command to extract the messages you added into the `public/locales/en-US/grafana.json` file and make them available for translation. + **Note:** All other languages will receive their translations when they are ready to be downloaded from Crowdin. ### Plain JS usage @@ -64,20 +66,20 @@ While the `t` function can technically be used outside of React functions (e.g, ## How to add a new language -1. Add new locale in Crowdin and sync files to repo +1. Add new locale in Crowdin and download files to repo 1. Grafana OSS Crowdin project -> "dot dot dot" menu in top right -> Target languages - 2. Grafana OSS Crowdin project -> Integrations -> Github -> Sync Now - 3. If Crowdin's locale code is different from our IETF language tag, add a custom mapping in Project Settings -> Language mapping -2. Update `public/app/core/internationalization/constants.ts` (add new constant, and add to `LOCALES`) -3. Update `public/locales/i18next-parser.config.js` to add the new locale to `locales` -4. Run `yarn i18n:extract` and commit the result + 2. If Crowdin's locale code is different from our IETF language tag, add a custom mapping in Project Settings -> Language mapping + 3. GH repo grafana/grafana -> Actions -> Choose `Crowdin Download Action` -> Run workflow -> Creates a PR automatically +2. Review the PR `I18n: Download translations from Crowdin` +3. Update `public/app/core/internationalization/constants.ts` (add new constant, and add to `LOCALES`) and add changes to the open PR +4. Approve and merge the PR ## How translations work in Grafana Grafana uses the [i18next](https://www.i18next.com/) framework for managing translating phrases in the Grafana frontend. It: - Marks up phrases within our code for extraction -- Extracts phrases into messages catalogues for translating in external systems +- Extracts phrases into the default messages catalogue for translating in external systems - Manages the user's locale and putting the translated phrases in the UI Grafana will load the message catalogue JSON before the initial render. diff --git a/crowdin.yml b/crowdin.yml index 1cdf69254df..ba8846585f1 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -2,7 +2,6 @@ files: - source: /public/locales/en-US/grafana.json translation: /public/locales/%locale%/%original_file_name% type: i18next_json -pull_request_title: 'I18n: Crowdin sync' -pull_request_labels: - - area/internationalization - - no-changelog +# The following are pulled from env variables +project_id_env: CROWDIN_PROJECT_ID +api_token_env: CROWDIN_PERSONAL_TOKEN diff --git a/public/locales/i18next-parser.config.js b/public/locales/i18next-parser.config.js index 0628220503a..f3f5bd6c9bc 100644 --- a/public/locales/i18next-parser.config.js +++ b/public/locales/i18next-parser.config.js @@ -1,8 +1,8 @@ module.exports = { // Default namespace used in your i18next config defaultNamespace: 'grafana', - - locales: ['en-US', 'fr-FR', 'es-ES', "de-DE", "zh-Hans", 'pseudo-LOCALE'], + // Adds changes only to en-US when extracting keys, every other language is provided by Crowdin + locales: ['en-US'], output: './public/locales/$LOCALE/$NAMESPACE.json',