
I18n Localization
Scan React, Vue, and Python code for hardcoded user-facing strings before you ship or expand to new locales.
Overview
i18n-localization is an agent skill most often used in Ship (also Build frontend, Launch geo) that detects hardcoded strings and missing translation usage in React, Vue, and Python codebases.
Install
npx skills add https://github.com/sickn33/antigravity-awesome-skills --skill i18n-localizationWhat is this skill?
- Python checker script scans JSX, Vue templates, and Python for hardcoded English-like strings
- Detects common anti-patterns: raw JSX text, title/placeholder/aria-label literals, Flask flash messages
- Cross-checks against i18n usage signals: `t()`, `useTranslation`, `$t()`, gettext `_()`
- Framework-oriented regex buckets for jsx, vue, and python file types
- UTF-8 safe console output for Unicode paths on Windows agents
- Scans three stack buckets: jsx, vue, and python pattern sets
Adoption & trust: 2k installs on skills.sh; 40.1k GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your UI still embeds English literals in components and server messages, so turning on a new locale will show broken or inconsistent copy.
Who is it for?
Indie builders with React, Vue, or Flask/Python UIs who already plan to use i18n libraries but need a fast audit pass from an agent.
Skip if: Teams wanting automatic extraction into PO files, RTL layout review, or legal locale compliance without manual follow-up fixes.
When should I use this skill?
You need to detect hardcoded user-facing strings or verify i18n helper usage before localization work or international launch.
What do I get? / Deliverables
You get a focused list of suspected hardcoded strings and files to refactor toward `t()`, `$t()`, or gettext before you cut a multilingual release.
- Report of suspected hardcoded strings by file
- Signal of existing i18n API usage patterns in scanned paths
Recommended Skills
Journey fit
Spans multiple journey phases - primary shelf plus alternate fits below.
i18n debt blocks safe releases; canonical shelf is Ship because the skill audits code readiness immediately before launch and ongoing releases, not initial ideation. Launch subphase here means release-readiness for global users—catching untranslated copy before it hits production listings and support load.
Where it fits
After adding a settings page, run the checker to ensure labels use `t()` instead of raw JSX text.
Gate a release candidate by scanning for English literals in Vue placeholders before production deploy.
Before enabling DE/FR locales, list Python flash messages still hardcoded for shoppers.
How it compares
A lightweight static checker skill—not a hosted TMS, Crowdin integration, or runtime locale fallback service.
Common Questions / FAQ
Who is i18n-localization for?
Solo developers shipping web or hybrid apps in React, Vue, or Python who want an agent-run scan for untranslated hardcoded text before going global.
When should I use i18n-localization?
Use it in Ship before release tags, during Build when touching user-visible strings, and at Launch when enabling geo-specific locales or App Store localizations.
Is i18n-localization safe to install?
The skill runs a local filesystem scan via Python; it does not require network by default—review the Security Audits panel on this Prism page before enabling shell execution in your repo.
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