Files
web-Iot/.workbuddy/fix_i18n.py

162 lines
6.8 KiB
Python
Raw Permalink Normal View History

2026-08-12 11:29:44 +08:00
"""
Fix script for i18n batch transformation issues:
1. Remove misplaced `const { t } = useTranslation();` lines (inside object literals, etc.)
2. Fix JSX attributes: `=t('...')` -> `={t('...')}`
3. Re-add the hook correctly in the main function component
"""
import re
import os
target_dirs = ['src/pages', 'src/components']
skip_files = [
'src/components/LanguageSwitcher.tsx',
'src/components/Layout.tsx',
'src/i18n/index.ts',
'src/locales/zh/index.ts',
'src/locales/en/index.ts',
]
def get_target_files():
files = []
for target_dir in target_dirs:
for root, dirs, filenames in os.walk(target_dir):
for fname in filenames:
if fname.endswith(('.tsx', '.ts', '.jsx', '.js')):
filepath = os.path.join(root, fname).replace('\\', '/')
if filepath not in skip_files:
files.append(filepath)
return files
target_files = get_target_files()
total_fixed = 0
for filepath in target_files:
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
except:
continue
original = content
fixes = 0
# Step 1: Remove ALL `const { t } = useTranslation();` lines
# These might be misplaced (inside object literals, etc.)
old_hook_line = '\tconst { t } = useTranslation();\n'
old_hook_line2 = '\n\tconst { t } = useTranslation();\n'
hook_count = content.count('const { t } = useTranslation();')
if hook_count > 0:
# Remove all instances
content = content.replace(old_hook_line, '')
content = content.replace(old_hook_line2, '\n')
# Also handle variations
content = re.sub(r'\n\s*const \{ t \} = useTranslation\(\);\n', '\n', content)
fixes += hook_count
# Step 2: Fix JSX attributes: `=t('...')` -> `={t('...')}`
# Pattern: word=t('...') where word is a JSX attribute name
# But NOT: key: t('...') (JS object property) or return t('...') or = t('...')
# JSX attributes look like: placeholder=t('key') or title=t('key')
# We need to match: identifier immediately followed by =t('
# Match patterns like: placeholder=t('some.key') or title=t('some.key')
# But not patterns like: :t(' or {t(' or (t(' or = t(' (with space)
def fix_jsx_attr(match):
prefix = match.group(1)
key = match.group(2)
return f'{prefix}={{t(\'{key}\')}}'
# This regex matches: word=t('key') where word is preceded by space or <
# and NOT preceded by : or { or (
content = re.sub(
r'(?<![{:((])(\b\w+)=t\(\'([^\']+)\'\)',
fix_jsx_attr,
content
)
# Also fix: word=t("key") with double quotes
content = re.sub(
r'(?<![{:((])(\b\w+)=t\("([^"]+)"\)',
lambda m: f'{m.group(1)}={{t(\'{m.group(2)}\')}}',
content
)
# Step 3: Re-add useTranslation import if file uses t() but doesn't have the import
has_t_calls = bool(re.search(r"\bt\(['\"]", content))
has_import = 'useTranslation' in content and 'import' in content
if has_t_calls and not has_import:
# Add import after last import line
import_pattern = r"((?:import .+?;\n)+)"
matches = list(re.finditer(import_pattern, content))
if matches:
last_import = matches[-1]
insert_pos = last_import.end()
content = content[:insert_pos] + "import { useTranslation } from 'react-i18next';\n" + content[insert_pos:]
fixes += 1
# Step 4: Re-add the hook in the correct place (main function component)
if has_t_calls and has_import or (has_t_calls and 'useTranslation' in content):
# Check if hook is already present
if 'const { t } = useTranslation();' not in content:
# Find the main function component and add the hook after its opening brace
# Pattern 1: export default function ComponentName() {
match = re.search(r'(export default function \w+\([^)]*\)\s*\{)', content)
if match:
insert_pos = match.end()
content = content[:insert_pos] + '\n\tconst { t } = useTranslation();' + content[insert_pos:]
fixes += 1
else:
# Pattern 2: function ComponentName() {
match = re.search(r'(function \w+\([^)]*\)\s*\{)', content)
if match:
insert_pos = match.end()
content = content[:insert_pos] + '\n\tconst { t } = useTranslation();' + content[insert_pos:]
fixes += 1
else:
# Pattern 3: const ComponentName = () => {
# or: const ComponentName = (props) => {
match = re.search(r'(const \w+\s*=\s*(?:\([^)]*\))?\s*=>\s*\{)', content)
if match:
insert_pos = match.end()
content = content[:insert_pos] + '\n\tconst { t } = useTranslation();' + content[insert_pos:]
fixes += 1
else:
# Pattern 4: export default () => {
match = re.search(r'(export default\s*(?:\([^)]*\))?\s*=>\s*\{)', content)
if match:
insert_pos = match.end()
content = content[:insert_pos] + '\n\tconst { t } = useTranslation();' + content[insert_pos:]
fixes += 1
# Step 5: Also check for files that have the import but the hook was removed
# Re-add if needed
if 'useTranslation' in content and 'import' in content and 'const { t } = useTranslation();' not in content:
if has_t_calls or re.search(r"\bt\(['\"]", content):
match = re.search(r'(export default function \w+\([^)]*\)\s*\{)', content)
if match:
insert_pos = match.end()
content = content[:insert_pos] + '\n\tconst { t } = useTranslation();' + content[insert_pos:]
fixes += 1
else:
match = re.search(r'(function \w+\([^)]*\)\s*\{)', content)
if match:
insert_pos = match.end()
content = content[:insert_pos] + '\n\tconst { t } = useTranslation();' + content[insert_pos:]
fixes += 1
else:
match = re.search(r'(const \w+\s*=\s*(?:\([^)]*\))?\s*=>\s*\{)', content)
if match:
insert_pos = match.end()
content = content[:insert_pos] + '\n\tconst { t } = useTranslation();' + content[insert_pos:]
fixes += 1
if content != original:
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
total_fixed += 1
print(f" Fixed: {filepath} ({fixes} issues)")
print(f"\nDone! Fixed {total_fixed} files")