
I18n Localization
Scan React, Vue, and Python codebases for hardcoded UI copy and missing translation keys before you ship multiple locales.
Overview
i18n-localization is an agent skill for the Build phase that scans React, Vue, and Python files for hardcoded strings and missing translation usage.
Install
npx skills add https://github.com/davila7/claude-code-templates --skill i18n-localizationWhat is this skill?
- Python i18n checker script scans repos for hardcoded English in JSX, Vue templates, and Python literals
- Regex families flag untranslated JSX text, attribute strings (title, placeholder, label), and flash/print messages
- Recognizes proper i18n usage patterns: react-i18next t(), useTranslation, Vue $t(), Python gettext _()
- Windows console UTF-8 handling for Unicode diagnostics in CI or local runs
- Structured output path (JSON-oriented workflow) for agent-driven fix lists
- Detection rule sets for jsx, vue, and python file classes
- Checks common i18n call patterns including t(), useTranslation, $t(), and _()
Adoption & trust: 525 installs on skills.sh; 27.8k GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You are adding locales but still have English baked into components, templates, and server messages with no systematic way to find them.
Who is it for?
Indie SaaS teams introducing or auditing i18n on React/Vue frontends with optional Python backend copy.
Skip if: Teams that only need runtime locale switching without static analysis, or projects already fully keyed with no string literals in UI.
When should I use this skill?
You are internationalizing a React, Vue, or Python app and need to catch user-visible literals still embedded in source.
What do I get? / Deliverables
You get a concrete list of likely untranslated strings and files so you can replace them with catalog keys before shipping.
- List of candidate hardcoded strings by file
- Contrast signal against existing i18n helper usage
Recommended Skills
Journey fit
Internationalization gaps show up while implementing UI and server messages, so the canonical shelf is Build → frontend even though you may re-run checks in Ship. The checker targets JSX/Vue templates and Flask-style strings—the work happens alongside component and template authoring.
How it compares
Use instead of manual grep for English sentences when you want framework-aware hardcoded-string heuristics.
Common Questions / FAQ
Who is i18n-localization for?
Solo and small-team builders maintaining multilingual web apps who want an agent-guided audit before merge or locale expansion.
When should I use i18n-localization?
During Build while editing frontend templates, before Ship when running pre-release QA, and when Validate prototypes need copy extracted into translation files.
Is i18n-localization safe to install?
It is a read-only filesystem scanner in the included script; review the Security Audits panel on this page before running in sensitive repos.
SKILL.md
READMESKILL.md - I18n Localization
#!/usr/bin/env python3 """ i18n Checker - Detects hardcoded strings and missing translations. Scans for untranslated text in React, Vue, and Python files. """ import sys import re import json from pathlib import Path # Fix Windows console encoding for Unicode output try: sys.stdout.reconfigure(encoding='utf-8', errors='replace') sys.stderr.reconfigure(encoding='utf-8', errors='replace') except AttributeError: pass # Python < 3.7 # Patterns that indicate hardcoded strings (should be translated) HARDCODED_PATTERNS = { 'jsx': [ # Text directly in JSX: <div>Hello World</div> r'>\s*[A-Z][a-zA-Z\s]{3,30}\s*</', # JSX attribute strings: title="Welcome" r'(title|placeholder|label|alt|aria-label)="[A-Z][a-zA-Z\s]{2,}"', # Button/heading text r'<(button|h[1-6]|p|span|label)[^>]*>\s*[A-Z][a-zA-Z\s!?.,]{3,}\s*</', ], 'vue': [ # Vue template text r'>\s*[A-Z][a-zA-Z\s]{3,30}\s*</', r'(placeholder|label|title)="[A-Z][a-zA-Z\s]{2,}"', ], 'python': [ # print/raise with string literals r'(print|raise\s+\w+)\s*\(\s*["\'][A-Z][^"\']{5,}["\']', # Flask flash messages r'flash\s*\(\s*["\'][A-Z][^"\']{5,}["\']', ] } # Patterns that indicate proper i18n usage I18N_PATTERNS = [ r't\(["\']', # t('key') - react-i18next r'useTranslation', # React hook r'\$t\(', # Vue i18n r'_\(["\']', # Python gettext r'gettext\(', # Python gettext r'useTranslations', # next-intl r'FormattedMessage', # react-intl r'i18n\.', # Generic i18n ] def find_locale_files(project_path: Path) -> list: """Find translation/locale files.""" patterns = [ "**/locales/**/*.json", "**/translations/**/*.json", "**/lang/**/*.json", "**/i18n/**/*.json", "**/messages/*.json", "**/*.po", # gettext ] files = [] for pattern in patterns: files.extend(project_path.glob(pattern)) return [f for f in files if 'node_modules' not in str(f)] def check_locale_completeness(locale_files: list) -> dict: """Check if all locales have the same keys.""" issues = [] passed = [] if not locale_files: return {'passed': [], 'issues': ["[!] No locale files found"]} # Group by parent folder (language) locales = {} for f in locale_files: if f.suffix == '.json': try: lang = f.parent.name content = json.loads(f.read_text(encoding='utf-8')) if lang not in locales: locales[lang] = {} locales[lang][f.stem] = set(flatten_keys(content)) except: continue if len(locales) < 2: passed.append(f"[OK] Found {len(locale_files)} locale file(s)") return {'passed': passed, 'issues': issues} passed.append(f"[OK] Found {len(locales)} language(s): {', '.join(locales.keys())}") # Compare keys across locales all_langs = list(locales.keys()) base_lang = all_langs[0] for namespace in locales.get(base_lang, {}): base_keys = locales[base_lang].get(namespace, set()) for lang in all_langs[1:]: other_keys = locales.get(lang, {}).get(namespace, set()) missing = base_keys - other_keys if missing: issues.append(f"[X] {lang}/{namespace}: Missing {len(missing)} keys") extra = other_keys - base_keys if extra: issues.append(f"[!] {lang}/{namespace}: {len(extra)} extra keys") if not issues: passed.append("[OK] All locales have matching keys") return {'passed': passed, 'issues': issues} def flatten_keys(d, prefix=''): """Flatten nested dict keys.""" keys = set() for k, v in d.items(): new_key = f"{prefix}.{k}" if