diff --git a/.github/templates/crowdin-pr.md b/.github/templates/crowdin-pr.md new file mode 100644 index 0000000000..a8f8980679 --- /dev/null +++ b/.github/templates/crowdin-pr.md @@ -0,0 +1,4 @@ +This pull request is created according to the `.github/workflows/i18n-pull.yml` file. + +- 🌐 [Contribute to translations on Crowdin](https://crowdin.com/project/modrinth) +- 🔄 [Dispatch this workflow again to update this PR](https://github.com/Modrinth/code/actions/workflows/i18n-pull.yml) \ No newline at end of file diff --git a/.github/workflows/i18n-pull.yml b/.github/workflows/i18n-pull.yml new file mode 100644 index 0000000000..14a1c814c9 --- /dev/null +++ b/.github/workflows/i18n-pull.yml @@ -0,0 +1,108 @@ +name: Crowdin (pull) + +on: + schedule: + - cron: '0 7 * * MON' # every monday at 7 am + workflow_dispatch: + +concurrency: + group: i18n-management + +jobs: + pull_translations: + name: 'Pull translations from Crowdin' + runs-on: ubuntu-22.04 + if: github.ref == 'refs/heads/main' + concurrency: + group: i18n-pull:${{ github.ref }} + cancel-in-progress: true + steps: + - name: Preflight check + run: | + PREFLIGHT_CHECK_RESULT=true + + function flight_failure () { + if [ "$PREFLIGHT_CHECK_RESULT" = true ]; then + echo "One or more pre-flight checks failed!" + echo "" + PREFLIGHT_CHECK_RESULT=false + fi + echo "- $1" + } + + if [ "$CROWDIN_PROJECT_ID_DEFINED" != true ]; then + flight_failure "CROWDIN_PROJECT_ID variable is not defined (required to push)" + fi + + if [ "$CROWDIN_PERSONAL_TOKEN_DEFINED" != true ]; then + flight_failure "CROWDIN_PERSONAL_TOKEN secret is not defined (required to push)" + fi + + if [ "$CROWDIN_GH_TOKEN_DEFINED" != true ]; then + flight_failure "CROWDIN_GH_TOKEN secret is not defined (required to make pull requests)" + fi + + if [ "$PREFLIGHT_CHECK_RESULT" = false ]; then + exit 1 + fi + env: + CROWDIN_PROJECT_ID_DEFINED: ${{ vars.CROWDIN_PROJECT_ID != '' }} + CROWDIN_PERSONAL_TOKEN_DEFINED: ${{ secrets.CROWDIN_PERSONAL_TOKEN != '' }} + CROWDIN_GH_TOKEN_DEFINED: ${{ secrets.CROWDIN_GH_TOKEN != '' }} + + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + token: ${{ secrets.CROWDIN_GH_TOKEN }} + + - name: Configure Git author + id: git-author + uses: MarcoIeni/git-config@v0.1 + env: + GITHUB_TOKEN: ${{ secrets.CROWDIN_GH_TOKEN }} + + # # Because --all flag of Crowdin CLI is currently broken we need to create a fake source file + # # so that the CLI won't omit translations for it. See https://github.com/crowdin/crowdin-cli/issues/724 + # - name: Write fake sources + # shell: bash + # run: echo "{}" > locales/en-US/index.json + + - name: Query branch name + id: branch-name + shell: bash + run: | + BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) + SAFE_BRANCH_NAME=$(echo "$BRANCH_NAME" | sed -e "s/[\\\\/\\:*?\"<>|]/_/g") + echo "Branch name is $BRANCH_NAME (escaped as $SAFE_BRANCH_NAME)" + echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" + echo "safe_branch_name=$SAFE_BRANCH_NAME" >> "$GITHUB_OUTPUT" + + - name: Download translations from Crowdin + uses: crowdin/github-action@v2 + with: + upload_sources: false + upload_translations: false + download_translations: true + push_translations: false + create_pull_request: false + crowdin_branch_name: '[${{ github.repository_owner }}.${{ github.event.repository.name }}] ${{ steps.branch-name.outputs.safe_branch_name }}' + env: + CROWDIN_PROJECT_ID: ${{ vars.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} + + - name: Fix broken permissions + shell: bash + run: sudo chown -R $USER:$USER . + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + title: 'New translations from Crowdin (${{ steps.branch-name.outputs.branch_name }})' + body-path: .github/templates/crowdin-pr.md + commit-message: 'New translations from Crowdin (${{ steps.branch-name.outputs.branch_name }})' + branch: crowdin-pull/${{ steps.branch-name.outputs.branch_name }} + author: '${{ steps.git-author.outputs.name }} <${{ steps.git-author.outputs.email }}>' + committer: '${{ steps.git-author.outputs.name }} <${{ steps.git-author.outputs.email }}>' + labels: sync + token: ${{ secrets.CROWDIN_GH_TOKEN }} diff --git a/.github/workflows/i18n-push.yml b/.github/workflows/i18n-push.yml new file mode 100644 index 0000000000..812b6e0d19 --- /dev/null +++ b/.github/workflows/i18n-push.yml @@ -0,0 +1,81 @@ +name: Crowdin (push) + +on: + push: + branches: ['main'] + paths: + - '.github/workflows/i18n.push.yml' + - 'apps/*/src/locales/en-US/**' + - 'apps/*/locales/en-US/**' + - 'packages/*/src/locales/en-US/**' + - 'packages/*/locales/en-US/**' + - 'crowdin.yml' + workflow_dispatch: + +concurrency: + group: i18n-management + +jobs: + push_translations: + name: Push sources to Crowdin + runs-on: ubuntu-22.04 + if: github.ref == 'refs/heads/main' + concurrency: + group: i18n-push:${{ github.ref }} + cancel-in-progress: true + steps: + - name: Preflight check + run: | + PREFLIGHT_CHECK_RESULT=true + + function flight_failure () { + if [ "$PREFLIGHT_CHECK_RESULT" = true ]; then + echo "One or more pre-flight checks failed!" + echo "" + PREFLIGHT_CHECK_RESULT=false + fi + echo "- $1" + } + + if [ "$CROWDIN_PROJECT_ID_DEFINED" != true ]; then + flight_failure "CROWDIN_PROJECT_ID variable is not defined (required to push)" + fi + + if [ "$CROWDIN_PERSONAL_TOKEN_DEFINED" != true ]; then + flight_failure "CROWDIN_PERSONAL_TOKEN secret is not defined (required to push)" + fi + + if [ "$PREFLIGHT_CHECK_RESULT" = false ]; then + exit 1 + fi + env: + CROWDIN_PROJECT_ID_DEFINED: ${{ vars.CROWDIN_PROJECT_ID != '' }} + CROWDIN_PERSONAL_TOKEN_DEFINED: ${{ secrets.CROWDIN_PERSONAL_TOKEN != '' }} + + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + + - name: Query branch name + id: branch-name + shell: bash + run: | + BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) + SAFE_BRANCH_NAME=$(echo "$BRANCH_NAME" | sed -e "s/[\\\\/\\:*?\"<>|]/_/g") + echo "Branch name is $BRANCH_NAME (escaped as $SAFE_BRANCH_NAME)" + echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" + echo "safe_branch_name=$SAFE_BRANCH_NAME" >> "$GITHUB_OUTPUT" + + - name: Upload translations to Crowdin + uses: crowdin/github-action@v1 + with: + upload_sources: true + upload_translations: false + download_translations: false + push_translations: false + create_pull_request: false + crowdin_branch_name: '[${{ github.repository_owner }}.${{ github.event.repository.name }}] ${{ steps.branch-name.outputs.safe_branch_name }}' + env: + CROWDIN_PROJECT_ID: ${{ vars.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} diff --git a/apps/frontend/crowdin.yml b/apps/frontend/crowdin.yml deleted file mode 100644 index e7c47c7daf..0000000000 --- a/apps/frontend/crowdin.yml +++ /dev/null @@ -1,8 +0,0 @@ -project_id: 518556 -preserve_hierarchy: true -commit_message: "[ci skip]" - -files: - - source: /locales/en-US/* - dest: /%original_file_name% - translation: /locales/%locale%/%original_file_name% diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 0000000000..abc0e56723 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,21 @@ +preserve_hierarchy: true +commit_message: '[ci skip]' + +files: + - source: /apps/app-frontend/src/locales/en-US/*.json + dest: /app/%original_file_name% + translation: /apps/app-frontend/src/locales/%locale%/%original_file_name% + skip_untranslated_strings: true + + - source: /apps/frontend/src/locales/en-US/*.json + dest: /web/%original_file_name% + translation: /apps/frontend/src/locales/%locale%/%original_file_name% + skip_untranslated_strings: true + + - source: /packages/ui/src/locales/en-US/*.json + dest: /ui/%original_file_name% + translation: /packages/ui/src/locales/%locale%/%original_file_name% + skip_untranslated_strings: true + +project_id_env: CROWDIN_PROJECT_ID +api_token_env: CROWDIN_PERSONAL_TOKEN \ No newline at end of file diff --git a/packages/ui/crowdin.yml b/packages/ui/crowdin.yml deleted file mode 100644 index 2817cde427..0000000000 --- a/packages/ui/crowdin.yml +++ /dev/null @@ -1,8 +0,0 @@ -project_id: 518556 -preserve_hierarchy: true -commit_message: '[ci skip]' - -files: - - source: /locales/en-US/* - dest: /%original_file_name% - translation: /locales/%locale%/%original_file_name%