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