162 lines
6.8 KiB
Python
162 lines
6.8 KiB
Python
"""
|
||
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")
|