Files
web-Iot/.workbuddy/fix_i18n.py
2026-08-12 11:29:44 +08:00

162 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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")