Files
Joseph MattielloandClaude Opus 5 b58b977e01 fix(i18n): patch upstream initial_sync regex, fail loudly without a key
Two review catches (#264), both verified.

The sample's placeholder substitution in intl/initial_sync.py is
r'/_core_name_(?=[/.])]' -- a stray ']' after the lookahead, asserting
the next character is both '/'-or-'.' and ']'. It matches nothing:

    as-shipped regex changed anything? False
    without stray ']' changed anything? True

So '_core_name_' survived into crowdin.yaml and the documented initial
setup would have uploaded to a Crowdin path literally named
'_core_name_'. The reset path further down the same file got the same
tightening right, and older copies (nestopia) use a working
r'/_core_name_', so it is an upstream typo rather than intent.

Second: the vendored scripts call subprocess.run() without check=True
throughout, so a failed Crowdin CLI invocation exits 0 and the workflow
step reports success -- meaning an unset secret gave a green run that
did nothing, contradicting this PR's claim that the not-set-up state is
visible. Each workflow now fails on an empty CROWDIN_API_KEY as its
first step.

That guard lives in our workflow files rather than as check=True edits
across five upstream scripts: it covers the failure that actually
happens, cannot be clobbered by the sync workflow's own
`git add intl/*_workflow.py`, and does not risk failing runs on benign
non-zero exits from the Crowdin CLI that we cannot characterise without
a working token.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-02 12:47:43 -04:00

107 lines
4.2 KiB
Python

#!/usr/bin/env python3
import re
import os
import shutil
import subprocess
import sys
import time
import urllib.request
import zipfile
import core_option_translation as t
# -------------------- MAIN -------------------- #
if __name__ == '__main__':
# Check Crowdin API Token and core name
try:
API_KEY = sys.argv[1]
CORE_NAME = t.clean_file_name(sys.argv[2])
OPTIONS_PATH = t.clean_file_name(sys.argv[3])
except IndexError as e:
print('Please provide Crowdin API Token, core name and path to the core options file!')
raise e
DIR_PATH = os.path.dirname(os.path.realpath(__file__))
YAML_PATH = os.path.join(DIR_PATH, 'crowdin.yaml')
# Apply Crowdin API Key
with open(YAML_PATH, 'r') as crowdin_config_file:
crowdin_config = crowdin_config_file.read()
crowdin_config = re.sub(r'"api_token": "_secret_"',
f'"api_token": "{API_KEY}"',
crowdin_config, 1)
# Upstream typo: a stray ']' after the lookahead made this regex
# unmatchable, so the placeholder survived and the initial upload
# went to a Crowdin path literally named '_core_name_'. Reported
# upstream; keep this patched until the sample is fixed.
crowdin_config = re.sub(r'/_core_name_(?=[/.])',
f'/{CORE_NAME}'
, crowdin_config)
with open(YAML_PATH, 'w') as crowdin_config_file:
crowdin_config_file.write(crowdin_config)
try:
jar_name = 'crowdin-cli.jar'
jar_path = os.path.join(DIR_PATH, jar_name)
crowdin_cli_file = 'crowdin-cli.zip'
crowdin_cli_url = 'https://downloads.crowdin.com/cli/v3/' + crowdin_cli_file
crowdin_cli_path = os.path.join(DIR_PATH, crowdin_cli_file)
# Download Crowdin CLI
if not os.path.isfile(os.path.join(DIR_PATH, jar_name)):
print('download crowdin-cli.jar')
urllib.request.urlretrieve(crowdin_cli_url, crowdin_cli_path)
with zipfile.ZipFile(crowdin_cli_path, 'r') as zip_ref:
jar_dir = os.path.join(DIR_PATH, zip_ref.namelist()[0])
for file in zip_ref.namelist():
if file.endswith(jar_name):
jar_file = file
break
zip_ref.extract(jar_file, path=DIR_PATH)
os.rename(os.path.join(DIR_PATH, jar_file), jar_path)
os.remove(crowdin_cli_path)
shutil.rmtree(jar_dir)
# Create JSON data
subprocess.run(['python3', 'intl/core_option_translation.py', OPTIONS_PATH, CORE_NAME])
print('upload source & translations *.json')
subprocess.run(['java', '-jar', jar_path, 'upload', 'sources', '--config', YAML_PATH])
subprocess.run(['java', '-jar', jar_path, 'upload', 'translations', '--config', YAML_PATH])
print('wait for crowdin server to process data')
time.sleep(10)
print('download translation *.json')
subprocess.run(['java', '-jar', jar_path, 'download', '--config', YAML_PATH])
# Reset Crowdin API Key
with open(YAML_PATH, 'r') as crowdin_config_file:
crowdin_config = crowdin_config_file.read()
crowdin_config = re.sub(r'"api_token": ".*"', '"api_token": "_secret_"', crowdin_config, 1)
# TODO This is technically not safe and could replace more than intended.
crowdin_config = re.sub(r'/' + re.escape(CORE_NAME) + r'(?=[/.])',
'/_core_name_',
crowdin_config)
with open(YAML_PATH, 'w') as crowdin_config_file:
crowdin_config_file.write(crowdin_config)
except Exception as e:
# Try really hard to reset Crowdin API Key
with open(YAML_PATH, 'r') as crowdin_config_file:
crowdin_config = crowdin_config_file.read()
crowdin_config = re.sub(r'"api_token": ".*?"',
'"api_token": "_secret_"',
crowdin_config, 1)
# TODO This is technically not safe and could replace more than intended.
crowdin_config = re.sub(r'/' + re.escape(CORE_NAME) + r'(?=[/.])',
'/_core_name_',
crowdin_config)
with open(YAML_PATH, 'w') as crowdin_config_file:
crowdin_config_file.write(crowdin_config)
raise e