From bf9aec097003ce8cfae451344a21e8d9f38a775d Mon Sep 17 00:00:00 2001 From: shaurye Date: Thu, 13 Aug 2026 14:47:58 -0400 Subject: [PATCH 1/3] @W-23849761 | Validate registry translation locale filenames Move dash-case locale enforcement from packaged app configuration translations to the registry manifest translations where it belongs. --- .../test-validate-registry-translations.sh | 66 +++++++++++++++++++ .github/scripts/test-validate-translations.sh | 9 ++- .../scripts/validate-registry-translations.sh | 47 +++++++++++++ .github/scripts/validate-translations.sh | 22 ------- .github/workflows/verify-zip.yml | 7 ++ CONTRIBUTING.md | 4 +- 6 files changed, 127 insertions(+), 28 deletions(-) create mode 100755 .github/scripts/test-validate-registry-translations.sh create mode 100755 .github/scripts/validate-registry-translations.sh diff --git a/.github/scripts/test-validate-registry-translations.sh b/.github/scripts/test-validate-registry-translations.sh new file mode 100755 index 0000000..d320bb0 --- /dev/null +++ b/.github/scripts/test-validate-registry-translations.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +VALIDATE="$SCRIPT_DIR/validate-registry-translations.sh" + +PASS=0 +FAIL=0 +TMPDIR_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMPDIR_ROOT"' EXIT + +assert_passes() { + local desc="$1" + shift + local translations_dir + translations_dir="$(mktemp -d "$TMPDIR_ROOT/translations.XXXXXX")" + for locale in "$@"; do + printf '{}\n' > "$translations_dir/$locale.json" + done + + local output + if output="$(bash "$VALIDATE" "$translations_dir" 2>&1)"; then + echo " PASS: $desc" + PASS=$((PASS + 1)) + else + echo " FAIL: $desc" + echo " output: $output" + FAIL=$((FAIL + 1)) + fi +} + +assert_rejects() { + local desc="$1" + local expect_substr="$2" + shift 2 + local translations_dir + translations_dir="$(mktemp -d "$TMPDIR_ROOT/translations.XXXXXX")" + for locale in "$@"; do + printf '{}\n' > "$translations_dir/$locale.json" + done + + local output rc=0 + output="$(bash "$VALIDATE" "$translations_dir" 2>&1)" || rc=$? + if [[ "$rc" -eq 1 && "$output" == *"$expect_substr"* ]]; then + echo " PASS: $desc" + PASS=$((PASS + 1)) + else + echo " FAIL: $desc (expected exit 1 containing '$expect_substr', got exit $rc)" + echo " output: $output" + FAIL=$((FAIL + 1)) + fi +} + +echo "=== registry translations validator tests ===" + +assert_passes "empty directory is valid" +assert_passes "all supported locale filenames are valid" \ + ar-MA de en-US es fr it ja ko nl pl pt zh-CN zh-TW +assert_rejects "underscore region separator is rejected" \ + "use BCP-47 dash form" en-US zh_CN +assert_rejects "unknown locale is rejected" \ + "Unsupported registry translation locale file(s): xx-YY" en-US xx-YY + +echo "" +echo "=== Results: $PASS passed, $FAIL failed ===" +[[ "$FAIL" -eq 0 ]] diff --git a/.github/scripts/test-validate-translations.sh b/.github/scripts/test-validate-translations.sh index ddf5b97..463c0c3 100755 --- a/.github/scripts/test-validate-translations.sh +++ b/.github/scripts/test-validate-translations.sh @@ -113,6 +113,10 @@ assert_passes "en-US + matching de" \ "tasks:en-US.json:$EN_BASIC" \ "tasks:de.json:$DE_BASIC" +assert_passes "app config locale filenames are not dash-case validated" \ + "tasks:en-US.json:$EN_BASIC" \ + "tasks:zh_CN.json:$EN_BASIC" + assert_passes "tasksList taskKey present in en-US" \ "tasks:en-US.json:$EN_BASIC" \ "tasksList:$TASKS_LIST_OK" @@ -140,11 +144,6 @@ assert_rejects "empty name in en-US is rejected" \ "missing/invalid name or description" \ 'tasks:en-US.json:{"tasks":{"setup_account":{"name":"","description":"d"}}}' -assert_rejects "unsupported locale filename is rejected" \ - "Unsupported locale file" \ - "tasks:en-US.json:$EN_BASIC" \ - "tasks:xx-YY.json:$EN_BASIC" - assert_rejects "tasksList taskKey missing from en-US is rejected" \ "not present in translations/en-US.json" \ "tasks:en-US.json:$EN_BASIC" \ diff --git a/.github/scripts/validate-registry-translations.sh b/.github/scripts/validate-registry-translations.sh new file mode 100755 index 0000000..a8bd491 --- /dev/null +++ b/.github/scripts/validate-registry-translations.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Validate locale filenames in commerce-apps-manifest/translations/. +# +# Usage: validate-registry-translations.sh +# +# Exit codes: +# 0 - locale filenames are valid +# 1 - one or more locale filenames are unsupported +# 2 - usage error (bad args or missing directory) + +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "Usage: $(basename "$0") " >&2 + exit 2 +fi + +translations_dir="$1" + +if [[ ! -d "$translations_dir" ]]; then + echo "Registry translations directory not found: $translations_dir" >&2 + exit 2 +fi + +# Set of supported manifest locales. Region separators use BCP-47 dash form. +SUPPORTED_LOCALES=("ar-MA" "de" "en-US" "es" "fr" "it" "ja" "ko" "nl" "pl" "pt" "zh-CN" "zh-TW") + +unsupported_locales=() +while IFS= read -r locale_file; do + locale="$(basename "$locale_file" .json)" + supported_locale=false + for supported in "${SUPPORTED_LOCALES[@]}"; do + if [[ "$locale" == "$supported" ]]; then + supported_locale=true + break + fi + done + [[ "$supported_locale" == "false" ]] && unsupported_locales+=("$locale") +done < <(find "$translations_dir" -mindepth 1 -maxdepth 1 -type f -name '*.json' | sort) + +if [[ ${#unsupported_locales[@]} -gt 0 ]]; then + echo "Unsupported registry translation locale file(s): ${unsupported_locales[*]} (supported: ${SUPPORTED_LOCALES[*]}; use BCP-47 dash form for region separators)" >&2 + exit 1 +fi + +locale_count="$(find "$translations_dir" -mindepth 1 -maxdepth 1 -type f -name '*.json' | wc -l | tr -d ' ')" +echo "Registry translation locale filenames are valid ($locale_count file(s))" diff --git a/.github/scripts/validate-translations.sh b/.github/scripts/validate-translations.sh index 6416329..da1f5a4 100755 --- a/.github/scripts/validate-translations.sh +++ b/.github/scripts/validate-translations.sh @@ -9,8 +9,6 @@ # 2 - usage error (bad args or unreadable input) # # Schema: -# - Locale filenames must be in the supported set (kept in sync with the -# "Supported locales" table in CONTRIBUTING.md). # - en-US.json is required when the directory exists. # - Each locale file must be a JSON object with a "tasks" object whose # entries each have non-empty "name" and "description" strings. @@ -36,10 +34,6 @@ if [[ ! -d "$cap_root" ]]; then exit 2 fi -# Set of supported BM locales — must match the table in CONTRIBUTING.md -# under "Localizing app-shipped strings > Supported locales". -SUPPORTED_LOCALES=("ar-MA" "de" "en-US" "es" "fr" "it" "ja" "ko" "nl" "pl" "pt" "zh-CN" "zh-TW") - translations_dir="$cap_root/app-configuration/translations" if [[ ! -d "$translations_dir" ]]; then @@ -117,22 +111,6 @@ if [[ -n "$shape_errors" ]]; then exit 1 fi -# Locale filenames must be in the supported set. -unsupported_locales=() -while IFS= read -r locale_file; do - fname="$(basename "$locale_file" .json)" - ok=false - for supported in "${SUPPORTED_LOCALES[@]}"; do - [[ "$fname" == "$supported" ]] && ok=true && break - done - [[ "$ok" == "false" ]] && unsupported_locales+=("$fname") -done < <(find "$translations_dir" -mindepth 1 -maxdepth 1 -type f -name '*.json') - -if [[ ${#unsupported_locales[@]} -gt 0 ]]; then - echo "Unsupported locale file(s) in app-configuration/translations/: ${unsupported_locales[*]} (supported: ${SUPPORTED_LOCALES[*]})" >&2 - exit 1 -fi - # tasksList.json taskKey coverage in en-US.json. tasks_list="$cap_root/app-configuration/tasksList.json" if [[ -f "$tasks_list" ]]; then diff --git a/.github/workflows/verify-zip.yml b/.github/workflows/verify-zip.yml index 5f2dad1..7532b21 100644 --- a/.github/workflows/verify-zip.yml +++ b/.github/workflows/verify-zip.yml @@ -5,6 +5,7 @@ on: types: [opened, reopened, synchronize, edited, ready_for_review] paths: - '**/*.zip' + - 'commerce-apps-manifest/translations/**' jobs: verify-zips: @@ -569,3 +570,9 @@ jobs: rm -rf "$tmpdir" done < changed_zips.txt + + - name: Step 10 - Validate registry translation locale filenames + shell: bash + run: | + bash ".github/scripts/validate-registry-translations.sh" \ + "commerce-apps-manifest/translations" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3cb042c..7c8fd34 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -513,7 +513,9 @@ Locale filenames are validated against the set of locales supported by Business | Chinese (Simplified) | `zh-CN.json` | | Chinese (Traditional) | `zh-TW.json` | -CI will reject locale files whose filenames are outside this set. +CI enforces this filename set for registry-level files under +`commerce-apps-manifest/translations/`. Locale filenames packaged under +`app-configuration/translations/` are not validated for dash case by the registry. --- From 027bf0f6257397b85a40474ad9e1327390b1e73c Mon Sep 17 00:00:00 2001 From: shaurye Date: Thu, 13 Aug 2026 15:15:34 -0400 Subject: [PATCH 2/3] @W-23849761 | Keep app translation locale validation Retain dash-case validation for packaged app configuration translations while adding the registry manifest check. --- .github/scripts/test-validate-translations.sh | 9 ++++---- .github/scripts/validate-translations.sh | 22 +++++++++++++++++++ CONTRIBUTING.md | 6 ++--- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/.github/scripts/test-validate-translations.sh b/.github/scripts/test-validate-translations.sh index 463c0c3..ddf5b97 100755 --- a/.github/scripts/test-validate-translations.sh +++ b/.github/scripts/test-validate-translations.sh @@ -113,10 +113,6 @@ assert_passes "en-US + matching de" \ "tasks:en-US.json:$EN_BASIC" \ "tasks:de.json:$DE_BASIC" -assert_passes "app config locale filenames are not dash-case validated" \ - "tasks:en-US.json:$EN_BASIC" \ - "tasks:zh_CN.json:$EN_BASIC" - assert_passes "tasksList taskKey present in en-US" \ "tasks:en-US.json:$EN_BASIC" \ "tasksList:$TASKS_LIST_OK" @@ -144,6 +140,11 @@ assert_rejects "empty name in en-US is rejected" \ "missing/invalid name or description" \ 'tasks:en-US.json:{"tasks":{"setup_account":{"name":"","description":"d"}}}' +assert_rejects "unsupported locale filename is rejected" \ + "Unsupported locale file" \ + "tasks:en-US.json:$EN_BASIC" \ + "tasks:xx-YY.json:$EN_BASIC" + assert_rejects "tasksList taskKey missing from en-US is rejected" \ "not present in translations/en-US.json" \ "tasks:en-US.json:$EN_BASIC" \ diff --git a/.github/scripts/validate-translations.sh b/.github/scripts/validate-translations.sh index da1f5a4..6416329 100755 --- a/.github/scripts/validate-translations.sh +++ b/.github/scripts/validate-translations.sh @@ -9,6 +9,8 @@ # 2 - usage error (bad args or unreadable input) # # Schema: +# - Locale filenames must be in the supported set (kept in sync with the +# "Supported locales" table in CONTRIBUTING.md). # - en-US.json is required when the directory exists. # - Each locale file must be a JSON object with a "tasks" object whose # entries each have non-empty "name" and "description" strings. @@ -34,6 +36,10 @@ if [[ ! -d "$cap_root" ]]; then exit 2 fi +# Set of supported BM locales — must match the table in CONTRIBUTING.md +# under "Localizing app-shipped strings > Supported locales". +SUPPORTED_LOCALES=("ar-MA" "de" "en-US" "es" "fr" "it" "ja" "ko" "nl" "pl" "pt" "zh-CN" "zh-TW") + translations_dir="$cap_root/app-configuration/translations" if [[ ! -d "$translations_dir" ]]; then @@ -111,6 +117,22 @@ if [[ -n "$shape_errors" ]]; then exit 1 fi +# Locale filenames must be in the supported set. +unsupported_locales=() +while IFS= read -r locale_file; do + fname="$(basename "$locale_file" .json)" + ok=false + for supported in "${SUPPORTED_LOCALES[@]}"; do + [[ "$fname" == "$supported" ]] && ok=true && break + done + [[ "$ok" == "false" ]] && unsupported_locales+=("$fname") +done < <(find "$translations_dir" -mindepth 1 -maxdepth 1 -type f -name '*.json') + +if [[ ${#unsupported_locales[@]} -gt 0 ]]; then + echo "Unsupported locale file(s) in app-configuration/translations/: ${unsupported_locales[*]} (supported: ${SUPPORTED_LOCALES[*]})" >&2 + exit 1 +fi + # tasksList.json taskKey coverage in en-US.json. tasks_list="$cap_root/app-configuration/tasksList.json" if [[ -f "$tasks_list" ]]; then diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7c8fd34..99adf36 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -513,9 +513,9 @@ Locale filenames are validated against the set of locales supported by Business | Chinese (Simplified) | `zh-CN.json` | | Chinese (Traditional) | `zh-TW.json` | -CI enforces this filename set for registry-level files under -`commerce-apps-manifest/translations/`. Locale filenames packaged under -`app-configuration/translations/` are not validated for dash case by the registry. +CI enforces this filename set for both registry-level files under +`commerce-apps-manifest/translations/` and locale files packaged under +`app-configuration/translations/`. --- From 12fe359dec969654c3b433d33012e87715a4a9de Mon Sep 17 00:00:00 2001 From: shaurye Date: Thu, 13 Aug 2026 16:03:59 -0400 Subject: [PATCH 3/3] @W-23849761 | Cover underscore ZIP locale rejection Lock in dash-form locale enforcement for app configuration translations with an explicit regression test. --- .github/scripts/test-validate-translations.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/scripts/test-validate-translations.sh b/.github/scripts/test-validate-translations.sh index ddf5b97..63dde6b 100755 --- a/.github/scripts/test-validate-translations.sh +++ b/.github/scripts/test-validate-translations.sh @@ -145,6 +145,11 @@ assert_rejects "unsupported locale filename is rejected" \ "tasks:en-US.json:$EN_BASIC" \ "tasks:xx-YY.json:$EN_BASIC" +assert_rejects "underscore locale filename is rejected" \ + "Unsupported locale file" \ + "tasks:en-US.json:$EN_BASIC" \ + "tasks:zh_CN.json:$EN_BASIC" + assert_rejects "tasksList taskKey missing from en-US is rejected" \ "not present in translations/en-US.json" \ "tasks:en-US.json:$EN_BASIC" \