From 95835b050dfe7e111a59a43a462b357693304478 Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Mon, 14 Sep 2026 12:42:44 -0400 Subject: [PATCH 1/6] Simplify CI by dropping the Docs Bot and dead i18n automation. Remove the never-run translate-docs workflow, migrate PR workflows to GITHUB_TOKEN, and delete unused translation scaffolding so fewer secrets and custom actions are required. Co-authored-by: Cursor --- .github/actions/app-token/action.yml | 59 ---- .github/workflows/context7.yml | 10 - .github/workflows/generate-toolkit-docs.yml | 33 +- .github/workflows/llmstxt.yml | 37 +-- .github/workflows/translate-docs.yml | 298 ------------------ .../update-design-system-dependency.yml | 14 +- README.md | 4 +- _dictionaries/es.ts | 36 --- _dictionaries/get-dictionary-client.ts | 19 -- _dictionaries/get-dictionary.ts | 20 +- _dictionaries/i18n-config.ts | 7 +- _dictionaries/pt-BR.ts | 36 --- app/_components/contact-email.tsx | 5 +- app/_components/translation-banner.tsx | 72 ----- app/layout.tsx | 14 +- app/not-found.tsx | 48 +-- biome.jsonc | 10 - package.json | 1 - scripts/i18n-sync/index.ts | 178 ----------- scripts/i18n-sync/prompts.ts | 124 -------- scripts/i18n-sync/translate.ts | 261 --------------- scripts/i18n-sync/types.ts | 32 -- scripts/i18n-sync/utils.ts | 116 ------- 23 files changed, 37 insertions(+), 1397 deletions(-) delete mode 100644 .github/actions/app-token/action.yml delete mode 100644 .github/workflows/translate-docs.yml delete mode 100644 _dictionaries/es.ts delete mode 100644 _dictionaries/get-dictionary-client.ts delete mode 100644 _dictionaries/pt-BR.ts delete mode 100644 app/_components/translation-banner.tsx delete mode 100644 scripts/i18n-sync/index.ts delete mode 100644 scripts/i18n-sync/prompts.ts delete mode 100644 scripts/i18n-sync/translate.ts delete mode 100644 scripts/i18n-sync/types.ts delete mode 100644 scripts/i18n-sync/utils.ts diff --git a/.github/actions/app-token/action.yml b/.github/actions/app-token/action.yml deleted file mode 100644 index c811148ba..000000000 --- a/.github/actions/app-token/action.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Get app token -description: > - Mint a short-lived installation token for the docs automation GitHub App and - resolve the git identity of its bot user. - -inputs: - app-id: - description: App ID or client ID of the docs automation app. - required: true - private-key: - description: Private key of the docs automation app. - required: true - -outputs: - token: - description: Installation token, scoped to this repository and valid for one hour. - value: ${{ steps.app-token.outputs.token }} - user-name: - description: Git user name of the app's bot user. - value: ${{ steps.bot-user.outputs.user-name }} - user-email: - description: Git email of the app's bot user, which links commits to the bot account. - value: ${{ steps.bot-user.outputs.user-email }} - committer: - description: Bot identity as a single `Name ` string, the form create-pull-request expects. - value: ${{ steps.bot-user.outputs.committer }} - -runs: - using: composite - steps: - - name: Mint an installation token - id: app-token - uses: actions/create-github-app-token@v2 - with: - # `app-id` takes either the numeric app ID or the app's client ID. - app-id: ${{ inputs.app-id }} - private-key: ${{ inputs.private-key }} - - - name: Resolve the bot user identity - id: bot-user - shell: bash - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - APP_SLUG: ${{ steps.app-token.outputs.app-slug }} - run: | - # Assign before echoing. `echo "id=$(gh api ...)"` reports echo's exit - # code, so a failed lookup would pass the step with an empty id, and - # the resulting address is one git accepts but GitHub cannot link to - # the bot — misattributed commits, no error anywhere. - id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id) - - name="${APP_SLUG}[bot]" - email="${id}+${APP_SLUG}[bot]@users.noreply.github.com" - - { - echo "user-name=${name}" - echo "user-email=${email}" - echo "committer=${name} <${email}>" - } >>"$GITHUB_OUTPUT" diff --git a/.github/workflows/context7.yml b/.github/workflows/context7.yml index 168010bc4..20377a840 100644 --- a/.github/workflows/context7.yml +++ b/.github/workflows/context7.yml @@ -1,25 +1,15 @@ name: Refresh Context7 library on: - push: - branches: - - main workflow_dispatch: -permissions: - contents: write - pull-requests: write - jobs: context7: name: Refresh Context7 library runs-on: ubuntu-latest steps: - - name: refresh context7 library run: | curl -X POST https://context7.com/api/refresh-library \ -H "Content-Type: application/json" \ -d '{"libraryName": "/arcadeai/docs"}' - - diff --git a/.github/workflows/generate-toolkit-docs.yml b/.github/workflows/generate-toolkit-docs.yml index 000e6e06f..6e8b63e31 100644 --- a/.github/workflows/generate-toolkit-docs.yml +++ b/.github/workflows/generate-toolkit-docs.yml @@ -9,6 +9,11 @@ on: repository_dispatch: types: [porter_deploy_succeeded] workflow_dispatch: + inputs: + public_catalog_url: + description: "Override public catalog URL (staging only; production is the default)" + required: false + type: string # 11:00 UTC = 3 AM PST / 4 AM PDT — late enough that DST drift doesn't matter. schedule: - cron: "0 11 * * *" @@ -67,11 +72,11 @@ jobs: --verbose \ --api-source public-catalog \ --llm-provider anthropic \ - --llm-model "$ANTHROPIC_MODEL" \ + --llm-model "claude-sonnet-4-6" \ --llm-api-key "$ANTHROPIC_API_KEY" \ --llm-max-tokens 8192 \ --llm-editor-provider anthropic \ - --llm-editor-model "$ANTHROPIC_EDITOR_MODEL" \ + --llm-editor-model "claude-sonnet-4-6" \ --llm-editor-api-key "$ANTHROPIC_API_KEY" \ --toolkit-concurrency 8 \ --llm-concurrency 15 \ @@ -84,35 +89,21 @@ jobs: working-directory: toolkit-docs-generator env: # Unset in normal runs: the generator defaults to the production - # experience API, which needs no credentials. Set the secret to - # point a run at staging or a local BFF. - PUBLIC_CATALOG_URL: ${{ secrets.PUBLIC_CATALOG_URL }} + # experience API. Pass public_catalog_url on workflow_dispatch to + # point a manual run at staging or a local BFF. + PUBLIC_CATALOG_URL: ${{ inputs.public_catalog_url }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - ANTHROPIC_MODEL: ${{ secrets.ANTHROPIC_MODEL || 'claude-sonnet-4-6' }} - # Stronger model for the secret-coherence editor. Keeps - # stale-secret cleanup precise instead of re-summarizing the whole - # artifact. - ANTHROPIC_EDITOR_MODEL: ${{ secrets.ANTHROPIC_EDITOR_MODEL || 'claude-sonnet-4-6' }} - name: Sync toolkit sidebar navigation run: pnpm exec tsx toolkit-docs-generator/scripts/sync-toolkit-sidebar.ts --remove-empty-sections=false --verbose - - name: Get app token - id: app-token - uses: ./.github/actions/app-token - with: - app-id: ${{ secrets.DOCS_BOT_CLIENT_ID }} - private-key: ${{ secrets.DOCS_BOT_PRIVATE_KEY }} - - name: Create pull request id: cpr uses: peter-evans/create-pull-request@v7 env: HUSKY: 0 with: - token: ${{ steps.app-token.outputs.token }} - author: ${{ steps.app-token.outputs.committer }} - committer: ${{ steps.app-token.outputs.committer }} + token: ${{ secrets.GITHUB_TOKEN }} commit-message: "[AUTO] Adding MCP Servers docs update" title: "[AUTO] Adding MCP Servers docs update" body: | @@ -139,7 +130,7 @@ jobs: echo "::warning::Could not request review on PR #${{ steps.cpr.outputs.pull-request-number }}: $(cat review-error.log)" fi env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload generation report if: always() diff --git a/.github/workflows/llmstxt.yml b/.github/workflows/llmstxt.yml index 3ff948187..cf33d7237 100644 --- a/.github/workflows/llmstxt.yml +++ b/.github/workflows/llmstxt.yml @@ -27,10 +27,6 @@ jobs: name: Generate LLMSTXT runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: - name: Use Node.js uses: actions/setup-node@v4 @@ -41,10 +37,7 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.ref || github.ref }} - # No credentials to persist: the app token is minted further down, - # once there is something to push. The commit step below supplies it - # to git explicitly. - persist-credentials: false + token: ${{ secrets.GITHUB_TOKEN }} - name: Install dependencies run: npm install -g pnpm @@ -66,36 +59,21 @@ jobs: echo "has_changes=false" >> $GITHUB_OUTPUT fi - - name: Get app token - if: steps.check-changes.outputs.has_changes == 'true' - id: app-token - uses: ./.github/actions/app-token - with: - app-id: ${{ secrets.DOCS_BOT_CLIENT_ID }} - private-key: ${{ secrets.DOCS_BOT_PRIVATE_KEY }} - - name: Commit changes to PR if: steps.check-changes.outputs.has_changes == 'true' && github.event_name == 'pull_request' - env: - GH_APP_TOKEN: ${{ steps.app-token.outputs.token }} - USER_NAME: ${{ steps.app-token.outputs.user-name }} - USER_EMAIL: ${{ steps.app-token.outputs.user-email }} - HEAD_REF: ${{ github.event.pull_request.head.ref }} run: | - git config user.name "$USER_NAME" - git config user.email "$USER_EMAIL" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" git add public/llms.txt git commit -m "🤖 Regenerate LLMs.txt" - git push "https://x-access-token:${GH_APP_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "HEAD:${HEAD_REF}" + git push - name: Create Pull Request (for scheduled/manual runs) if: steps.check-changes.outputs.has_changes == 'true' && github.event_name != 'pull_request' id: cpr uses: peter-evans/create-pull-request@v7 with: - token: ${{ steps.app-token.outputs.token }} - author: ${{ steps.app-token.outputs.committer }} - committer: ${{ steps.app-token.outputs.committer }} + token: ${{ secrets.GITHUB_TOKEN }} commit-message: Regenerate LLMs.txt and related files branch: auto-update-llms-txt delete-branch: true @@ -109,10 +87,11 @@ jobs: continue-on-error: true run: gh pr edit ${{ steps.cpr.outputs.pull-request-number }} --add-reviewer ArcadeAI/engineering-tools-and-dx env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Enable Pull Request Automerge if: steps.check-changes.outputs.has_changes == 'true' && github.event_name != 'pull_request' && steps.cpr.outputs.pull-request-number != '' + continue-on-error: true run: gh pr merge --squash --auto ${{ steps.cpr.outputs.pull-request-number }} env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/translate-docs.yml b/.github/workflows/translate-docs.yml deleted file mode 100644 index 9741fc949..000000000 --- a/.github/workflows/translate-docs.yml +++ /dev/null @@ -1,298 +0,0 @@ -name: 📖 Translate Documentation - -on: - workflow_dispatch: - inputs: - target_locale: - description: "Target locale to translate" - required: false - default: "all" - type: choice - options: - - "all" - - "es" - - "pt-BR" - - force_translate: - description: "Force translate all files (ignore cache)" - required: false - default: false - type: boolean - - cleanup_deleted: - description: "Delete translated files when English originals are removed" - required: false - default: true - type: boolean - - dry_run: - description: "Dry run (don't write files or update cache)" - required: false - default: false - type: boolean - - single_file: - description: "Single file to translate (relative to app/en, e.g. 'resources/examples/page.mdx')" - required: false - default: "" - type: string - - concurrency: - description: "Number of files to translate in parallel (1-10)" - required: false - default: 3 - type: number - -jobs: - translate: - runs-on: ubuntu-latest - - steps: - - name: 🛒 Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: 📦 Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "pnpm" - - - name: 📥 Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9 - - - name: 📚 Install dependencies - run: pnpm install --frozen-lockfile - - - name: 🧹 Clean up deleted files - if: inputs.cleanup_deleted - run: | - echo "🔍 Checking for deleted English files..." - pnpm exec tsx scripts/i18n-sync/index.ts --cleanup - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - - - name: 🌍 Run translation - run: | - echo "🚀 Starting translation process..." - - # Build the command with dynamic inputs - CMD="pnpm exec tsx scripts/i18n-sync/index.ts" - - # Add target locale if not 'all' - if [ "${{ inputs.target_locale }}" != "all" ]; then - CMD="$CMD --locale ${{ inputs.target_locale }}" - fi - - # Add force flag if enabled - if [ "${{ inputs.force_translate }}" = "true" ]; then - CMD="$CMD --force" - fi - - # Add dry-run flag if enabled - if [ "${{ inputs.dry_run }}" = "true" ]; then - CMD="$CMD --dry-run" - fi - - # Add single file if specified - if [ -n "${{ inputs.single_file }}" ]; then - CMD="$CMD --file '${{ inputs.single_file }}'" - fi - - # Add concurrency - CMD="$CMD --concurrency ${{ inputs.concurrency }}" - - echo "💻 Executing: $CMD" - eval $CMD - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - OPENAI_MODEL: ${{ secrets.OPENAI_MODEL || 'gpt-4o-mini' }} - - - name: 📊 Check translation status - id: check_changes - run: | - if git diff --quiet && git diff --cached --quiet; then - echo "No changes detected" - echo "has_changes=false" >> $GITHUB_OUTPUT - else - echo "Changes detected" - echo "has_changes=true" >> $GITHUB_OUTPUT - - # Count changed files - CHANGED_FILES=$(git diff --name-only | wc -l) - STAGED_FILES=$(git diff --cached --name-only | wc -l) - TOTAL_CHANGES=$((CHANGED_FILES + STAGED_FILES)) - - echo "changed_files=$TOTAL_CHANGES" >> $GITHUB_OUTPUT - - # Get list of changed locales - CHANGED_LOCALES=$(git diff --name-only | grep -E '^app/(es|pt-BR)/' | cut -d'/' -f2 | sort -u | tr '\n' ',' | sed 's/,$//') - if [ -z "$CHANGED_LOCALES" ]; then - CHANGED_LOCALES=$(git diff --cached --name-only | grep -E '^app/(es|pt-BR)/' | cut -d'/' -f2 | sort -u | tr '\n' ',' | sed 's/,$//') - fi - echo "changed_locales=$CHANGED_LOCALES" >> $GITHUB_OUTPUT - fi - - - name: 🏷️ Generate branch name - if: steps.check_changes.outputs.has_changes == 'true' - id: branch_name - run: | - TIMESTAMP=$(date +%Y%m%d-%H%M%S) - if [ "${{ inputs.target_locale }}" != "all" ]; then - BRANCH_NAME="translations/${{ inputs.target_locale }}-$TIMESTAMP" - else - BRANCH_NAME="translations/all-locales-$TIMESTAMP" - fi - echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT - - - name: 🌿 Create and switch to new branch - if: steps.check_changes.outputs.has_changes == 'true' - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git checkout -b ${{ steps.branch_name.outputs.branch_name }} - - - name: 📝 Commit changes - if: steps.check_changes.outputs.has_changes == 'true' - run: | - git add . - - # Create detailed commit message - COMMIT_MSG="🌍 Update translations" - - if [ "${{ inputs.target_locale }}" != "all" ]; then - COMMIT_MSG="$COMMIT_MSG for ${{ inputs.target_locale }}" - fi - - COMMIT_MSG="$COMMIT_MSG (${{ steps.check_changes.outputs.changed_files }} files)" - - if [ "${{ inputs.force_translate }}" = "true" ]; then - COMMIT_MSG="$COMMIT_MSG [forced]" - fi - - if [ "${{ inputs.cleanup_deleted }}" = "true" ]; then - COMMIT_MSG="$COMMIT_MSG [with cleanup]" - fi - - if [ "${{ inputs.dry_run }}" = "true" ]; then - COMMIT_MSG="$COMMIT_MSG [dry-run]" - fi - - # Add configuration details to commit body - COMMIT_BODY="Translation Configuration: - - Target Locale: ${{ inputs.target_locale }} - - Force Translate: ${{ inputs.force_translate }} - - Cleanup Deleted: ${{ inputs.cleanup_deleted }} - - Dry Run: ${{ inputs.dry_run }} - - Single File: ${{ inputs.single_file || 'none' }} - - Concurrency: ${{ inputs.concurrency }} - - Changed Locales: ${{ steps.check_changes.outputs.changed_locales }} - - Generated by GitHub Actions workflow" - - git commit -m "$COMMIT_MSG" -m "$COMMIT_BODY" - - - name: 📤 Push branch - if: steps.check_changes.outputs.has_changes == 'true' - run: | - git push origin ${{ steps.branch_name.outputs.branch_name }} - - - name: 🔧 Create Pull Request - if: steps.check_changes.outputs.has_changes == 'true' - uses: actions/github-script@v7 - with: - script: | - const { data: pr } = await github.rest.pulls.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: `🌍 Translation Update: ${{ inputs.target_locale }} (${{ steps.check_changes.outputs.changed_files }} files)`, - head: '${{ steps.branch_name.outputs.branch_name }}', - base: 'main', - body: `## 📖 Automated Translation Update - - This PR contains automated translations generated by the GitHub Actions workflow. - - ### 📊 Translation Summary - - **Target Locale**: ${{ inputs.target_locale }} - - **Files Changed**: ${{ steps.check_changes.outputs.changed_files }} - - **Locales Updated**: ${{ steps.check_changes.outputs.changed_locales }} - - ### ⚙️ Configuration Used - - **Force Translate**: ${{ inputs.force_translate }} - - **Cleanup Deleted Files**: ${{ inputs.cleanup_deleted }} - - **Dry Run**: ${{ inputs.dry_run }} - - **Single File**: ${{ inputs.single_file || 'None (all files)' }} - - **Concurrency**: ${{ inputs.concurrency }} - - **Model**: gpt-4o-mini - - ### 🔍 Review Guidelines - Please review the translations for: - - [ ] Accuracy and context preservation - - [ ] Proper handling of technical terms - - [ ] UI/Dashboard elements remain in English - - [ ] Code blocks and inline code unchanged - - [ ] Markdown formatting preserved - - [ ] Brand names (Arcade, Arcade Engine, Control Plane) kept in English - - ### 🚀 Auto-generated - This PR was automatically created by the \`translate-docs.yml\` GitHub Action. - - **Triggered by**: @${{ github.actor }} - **Workflow Run**: [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})`, - draft: false - }); - - // Add labels - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - labels: ['🌍 translation', '🤖 automated', 'documentation'] - }); - - // Add specific locale label if not 'all' - if ('${{ inputs.target_locale }}' !== 'all') { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - labels: [`locale:${{ inputs.target_locale }}`] - }); - } - - console.log(`Created PR #${pr.number}: ${pr.html_url}`); - - - name: 📄 Summary - if: always() - run: | - echo "## 🌍 Translation Workflow Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "${{ steps.check_changes.outputs.has_changes }}" = "true" ]; then - echo "✅ **Translation completed successfully!**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Files changed**: ${{ steps.check_changes.outputs.changed_files }}" >> $GITHUB_STEP_SUMMARY - echo "- **Target locale**: ${{ inputs.target_locale }}" >> $GITHUB_STEP_SUMMARY - echo "- **Branch created**: \`${{ steps.branch_name.outputs.branch_name }}\`" >> $GITHUB_STEP_SUMMARY - echo "- **Changed locales**: ${{ steps.check_changes.outputs.changed_locales }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "A Pull Request has been created for review." >> $GITHUB_STEP_SUMMARY - else - echo "ℹ️ **No changes detected**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "All translations are up to date. No PR was created." >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Configuration Used" >> $GITHUB_STEP_SUMMARY - echo "- Target Locale: ${{ inputs.target_locale }}" >> $GITHUB_STEP_SUMMARY - echo "- Force Translate: ${{ inputs.force_translate }}" >> $GITHUB_STEP_SUMMARY - echo "- Cleanup Deleted: ${{ inputs.cleanup_deleted }}" >> $GITHUB_STEP_SUMMARY - echo "- Dry Run: ${{ inputs.dry_run }}" >> $GITHUB_STEP_SUMMARY - echo "- Single File: ${{ inputs.single_file || 'None' }}" >> $GITHUB_STEP_SUMMARY - echo "- Concurrency: ${{ inputs.concurrency }}" >> $GITHUB_STEP_SUMMARY - echo "- Model: gpt-4o-mini" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/update-design-system-dependency.yml b/.github/workflows/update-design-system-dependency.yml index 50f33522b..756bd95cc 100644 --- a/.github/workflows/update-design-system-dependency.yml +++ b/.github/workflows/update-design-system-dependency.yml @@ -47,14 +47,6 @@ jobs: pnpm vitest run toolkit-docs-generator/tests/scripts/sync-toolkit-sidebar.test.ts toolkit-docs-generator/tests/sources/oauth-provider-resolver.test.ts pnpm run build - - name: Get app token - if: steps.check-changes.outputs.has_changes == 'true' - id: app-token - uses: ./.github/actions/app-token - with: - app-id: ${{ secrets.DOCS_BOT_CLIENT_ID }} - private-key: ${{ secrets.DOCS_BOT_PRIVATE_KEY }} - - name: Create pull request if: steps.check-changes.outputs.has_changes == 'true' id: cpr @@ -63,9 +55,7 @@ jobs: HUSKY: 0 SKIP_HUSKY: 1 with: - token: ${{ steps.app-token.outputs.token }} - author: ${{ steps.app-token.outputs.committer }} - committer: ${{ steps.app-token.outputs.committer }} + token: ${{ secrets.GITHUB_TOKEN }} commit-message: "chore: update @arcadeai/design-system to latest" title: "chore: update @arcadeai/design-system to latest" body: | @@ -87,4 +77,4 @@ jobs: continue-on-error: true run: gh pr edit ${{ steps.cpr.outputs.pull-request-number }} --add-reviewer ArcadeAI/engineering-tools-and-dx env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 33d97c1d2..fe5b7ed54 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ Then, run `pnpm dev` to start the development server and visit localhost:3000. ### Environment Variables -Copy `.env.example` to `.env.local` and fill in the values: +Copy `.env.local.example` to `.env.local` and fill in the values: ```bash -cp .env.example .env.local +cp .env.local.example .env.local ``` | Variable | Required | Purpose | diff --git a/_dictionaries/es.ts b/_dictionaries/es.ts deleted file mode 100644 index 2c48bbf73..000000000 --- a/_dictionaries/es.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Dictionary } from "./i18n-config"; - -export default { - dark: "Oscuro", - light: "Luz", - system: "Sistema", - toc: { - title: "En esta página", - backToTop: "Desplazarse hacia arriba", - }, - lastUpdated: "Última actualización el", - notFound: "Esta página no se pudo encontrar", - poweredBy: "Desarrollado por", - editPage: "Edite esta página en GitHub →", - by: "por", - banner: { - aiTranslation: - "🤖 Esta traducción está en progreso y fue generada por IA. Si encuentras algo incorrecto o quieres ayudar a mejorarla, por favor", - contributeLink: "contribuye en GitHub", - thanks: "¡Gracias por tu ayuda!", - }, - notFoundPage: { - title: "Página no encontrada", - description: "Esta página no existe o puede haber sido movida.", - notAvailablePrefix: "No disponible en", - tryEnglish: "Probar versión en inglés", - translationHint: "Es posible que esta página aún no esté traducida.", - viewOriginalEnglish: "Ver el original en inglés", - goHome: "Ir a la página principal", - goBack: "Volver", - needHelp: "¿Necesitas ayuda? Prueba estas páginas populares:", - quickstart: "Inicio rápido", - mcpServers: "MCP Servers", - createMcpServer: "Crear un MCP Server", - }, -} satisfies Dictionary; diff --git a/_dictionaries/get-dictionary-client.ts b/_dictionaries/get-dictionary-client.ts deleted file mode 100644 index 15b5bd1f9..000000000 --- a/_dictionaries/get-dictionary-client.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Dictionaries, Dictionary, Locale } from "./i18n-config"; - -// Client-safe dictionary loader (without server-only restriction) -const dictionaries: Dictionaries = { - en: () => import("./en"), - es: () => import("./es"), - "pt-BR": () => import("./pt-BR"), -}; - -export async function getDictionaryClient(locale: string): Promise { - const localeKey: Locale = (Object.keys(dictionaries) as Locale[]).includes( - locale as Locale - ) - ? (locale as Locale) - : "en"; - const { default: dictionary } = await dictionaries[localeKey](); - - return dictionary; -} diff --git a/_dictionaries/get-dictionary.ts b/_dictionaries/get-dictionary.ts index 30ca47f95..6ef9aaa7f 100644 --- a/_dictionaries/get-dictionary.ts +++ b/_dictionaries/get-dictionary.ts @@ -1,21 +1,7 @@ import "server-only"; -import type { Dictionaries, Dictionary, Locale } from "./i18n-config"; - -// We enumerate all dictionaries here for better linting and TypeScript support -// We also get the default import for cleaner types -const dictionaries: Dictionaries = { - en: () => import("./en"), - es: () => import("./es"), - "pt-BR": () => import("./pt-BR"), -}; - -export async function getDictionary(locale: string): Promise { - const localeKey: Locale = (Object.keys(dictionaries) as Locale[]).includes( - locale as Locale - ) - ? (locale as Locale) - : "en"; - const { default: dictionary } = await dictionaries[localeKey](); +import type { Dictionary } from "./i18n-config"; +export async function getDictionary(_locale?: string): Promise { + const { default: dictionary } = await import("./en"); return dictionary; } diff --git a/_dictionaries/i18n-config.ts b/_dictionaries/i18n-config.ts index 82ab483f7..2ec8afc92 100644 --- a/_dictionaries/i18n-config.ts +++ b/_dictionaries/i18n-config.ts @@ -2,14 +2,9 @@ import type EnglishLocale from "./en"; export const i18n = { defaultLocale: "en", - locales: ["en", "es", "pt-BR"], + locales: ["en"], } as const; export type Locale = (typeof i18n)["locales"][number]; export type Dictionary = typeof EnglishLocale; - -export type Dictionaries = Record< - Locale, - () => Promise<{ default: Dictionary }> ->; diff --git a/_dictionaries/pt-BR.ts b/_dictionaries/pt-BR.ts deleted file mode 100644 index 3d3e42c88..000000000 --- a/_dictionaries/pt-BR.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Dictionary } from "./i18n-config"; - -export default { - dark: "Escuro", - light: "Claro", - system: "Sistema", - toc: { - title: "Nesta página", - backToTop: "Voltar ao topo", - }, - lastUpdated: "Última atualização em", - notFound: "Esta página não pôde ser encontrada", - poweredBy: "Desenvolvido por", - editPage: "Edite esta página no GitHub →", - by: "por", - banner: { - aiTranslation: - "🤖 Esta tradução está em andamento e foi gerada por IA. Se você encontrar algo incorreto ou quiser ajudar a melhorá-la, por favor", - contributeLink: "contribua no GitHub", - thanks: "Obrigado pela sua ajuda!", - }, - notFoundPage: { - title: "Página não encontrada", - description: "Esta página não existe ou pode ter sido movida.", - notAvailablePrefix: "Não disponível em", - tryEnglish: "Tentar versão em inglês", - translationHint: "Esta página pode ainda não estar traduzida.", - viewOriginalEnglish: "Ver o original em inglês", - goHome: "Ir para a página inicial", - goBack: "Voltar", - needHelp: "Precisa de ajuda? Experimente estas páginas populares:", - quickstart: "Início rápido", - mcpServers: "MCP Servers", - createMcpServer: "Criar um MCP Server", - }, -} satisfies Dictionary; diff --git a/app/_components/contact-email.tsx b/app/_components/contact-email.tsx index 075b31297..4896ca845 100644 --- a/app/_components/contact-email.tsx +++ b/app/_components/contact-email.tsx @@ -4,10 +4,7 @@ import Link from "next/link"; import type { ReactNode } from "react"; import { useEffect, useState } from "react"; -// TODO(i18n): hardcoded to the English contact page. Docs content is en-only -// today, so this is the safe target (a // link could 404 for locales -// without the page). When translated content ships, derive the active locale -// (e.g. via usePathname) and point at //resources/contact-us. +// Docs are English-only under /en. const CONTACT_PAGE = "/en/resources/contact-us"; const linkClassName = diff --git a/app/_components/translation-banner.tsx b/app/_components/translation-banner.tsx deleted file mode 100644 index 4dcdb884f..000000000 --- a/app/_components/translation-banner.tsx +++ /dev/null @@ -1,72 +0,0 @@ -"use client"; - -import { cn } from "@arcadeai/design-system/lib/utils"; -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { Banner } from "nextra/components"; - -type TranslationBannerProps = { - dictionary: { - banner: { - aiTranslation: string; - contributeLink: string; - thanks: string; - }; - }; - locale: string; -}; - -/** - * Constructs the GitHub edit URL for the current page - * Example: /es/home/auth -> https://github.com/ArcadeAI/docs/blob/main/app/es/home/auth/page.mdx - */ -function constructGithubUrl(pathname: string, locale: string): string { - const baseUrl = "https://github.com/ArcadeAI/docs/blob/main/app"; - - // Remove leading slash if present - const cleanPath = pathname.startsWith("/") ? pathname.slice(1) : pathname; - - // If the path doesn't start with the locale, add it - const pathWithLocale = cleanPath.startsWith(`${locale}/`) - ? cleanPath - : `${locale}/${cleanPath}`; - - // Construct the full GitHub URL pointing to the page.mdx file - return `${baseUrl}/${pathWithLocale}/page.mdx`; -} - -export function TranslationBanner({ - dictionary, - locale, -}: TranslationBannerProps) { - const pathname = usePathname(); - const githubUrl = constructGithubUrl(pathname || "/", locale); - - return ( - -
- {dictionary.banner.aiTranslation} - - {dictionary.banner.contributeLink} - - . {dictionary.banner.thanks} -
-
- ); -} diff --git a/app/layout.tsx b/app/layout.tsx index 12837dabe..bb9aab73f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -6,7 +6,6 @@ import { getDashboardUrl } from "@/app/_components/dashboard-link"; import { Footer } from "@/app/_components/footer"; import { Logo } from "@/app/_components/logo"; import NavBarButton from "@/app/_components/nav-bar-button"; -import { TranslationBanner } from "@/app/_components/translation-banner"; import "@/app/globals.css"; import { Discord, Github } from "@arcadeai/design-system"; import { GoogleTagManager } from "@next/third-parties/google"; @@ -96,15 +95,7 @@ export default async function RootLayout({ }: { children: React.ReactNode; }) { - // proxy.ts redirects every request to a "/en/..." path — "es" and - // "pt-BR" routes bounce to their "/en" equivalent and unlocaled routes - // pick up "/en" from getPreferredLocale, which is hardcoded to return - // "en" unconditionally. So this layout only ever renders under "/en", - // and reading the locale here can be a constant instead of a header - // lookup. Awaiting headers() in the root layout previously forced the - // entire route tree into dynamic rendering. Restoring real i18n means - // moving this layout under an `app/[lang]/` route segment so the - // locale comes from routing params, not a request header. + // Docs are English-only. proxy.ts redirects legacy /es and /pt-BR URLs to /en. const lang = "en"; const dictionary = await getDictionary(lang); @@ -149,9 +140,6 @@ export default async function RootLayout({ - {lang !== "en" && ( - - )} (null); const [dict, setDict] = useState(defaultDict); - const { currentLocale, englishPath, showEnglishLink } = useMemo(() => { - const locale = pathname.match(LOCALE_PATH_REGEX)?.[1] || "en"; - const enPath = `/en${pathname.replace(LOCALE_PREFIX_REGEX, "") || "/"}`; - const showEnLink = locale !== "en"; - - return { - currentLocale: locale, - englishPath: enPath, - showEnglishLink: showEnLink, - }; - }, [pathname]); + const currentLocale = useMemo( + () => pathname.match(LOCALE_PATH_REGEX)?.[1] || "en", + [pathname] + ); - // Load dictionary on client side useEffect(() => { const loadDict = async () => { try { - type DictModule = { default: Dictionary }; - let dictModule: DictModule; - if (currentLocale === "es") { - dictModule = await import("@/_dictionaries/es"); - } else if (currentLocale === "pt-BR") { - dictModule = await import("@/_dictionaries/pt-BR"); - } else { - dictModule = await import("@/_dictionaries/en"); - } + const dictModule = await import("@/_dictionaries/en"); setDict(dictModule.default.notFoundPage); } catch { // Keep default English dictionary on error } }; loadDict(); - }, [currentLocale]); + }, []); useEffect(() => { const search = searchParams?.toString(); @@ -105,14 +87,12 @@ function NotFoundContent() { pathname, path: pathWithQuery, locale: currentLocale, - english_path: englishPath, - show_english_link: showEnglishLink, // Ensure special props are attached to custom 404 events for link analysis. $current_url: typeof window !== "undefined" ? window.location.href : null, $referrer: referrer || null, $referring_domain: referringDomain || null, }); - }, [currentLocale, englishPath, pathname, searchParams, showEnglishLink]); + }, [currentLocale, pathname, searchParams]); return (
@@ -136,20 +116,6 @@ function NotFoundContent() {

- {/* English fallback */} - {showEnglishLink && ( -
- {dict.translationHint} - - {dict.viewOriginalEnglish} - - . -
- )} - {/* Actions */}