1. The challenge
Our engineering team faced a massive manual bottleneck when expanding our React + TypeScript platform to global markets. Every single feature release forced developers into a repetitive, exhausting cycle: hunting down naked static text strings hidden across the codebase, manually defining keys, updating localization files, logging into the Lokalise platform to upload them, and finally rewriting the source code.
The impact
- Time drain. Localizing a single feature ate up 4 to 5 hours of core engineering time.
- Inflated costs. Different developers inadvertently created duplicate keys for identical text strings, pushing us into higher, costlier Lokalise pricing tiers.
- Seat bottlenecks. Limited platform seats meant developers constantly had to wait for authorized team members to manually upload files.
2. The strategy and selection
We evaluated two paths to eliminate this operational friction:
- Option A: Off-the-shelf extraction frameworks (like i18next-parser). We quickly ruled this out. Traditional tools only extract text that is already wrapped in translation functions or components. They completely miss unwrapped static text hidden in object mappings, plain arrays, button labels, and input fields.
- Option B: A custom AST-based Babel parser. We greenlit a custom approach because our codebase leverages structural React and strict TypeScript. Building a custom script allowed us to parse the code’s Abstract Syntax Tree (AST), target raw, untagged strings, verify them against our existing database to prevent duplication, and refactor the code automatically.
3. The implementation
We built an automated engineering workflow powered by Babel, controlled via simple npm commands.
Phase 1: Deep code scanning
npm run findStaticText
The script traverses the AST to locate all raw static text, successfully extracting strings from complex, varied syntax shapes:
- JSX attributes.
<input placeholder="Enter name" /> - Plain objects.
const messages = { success: "Project created successfully" }; - JSX children.
<button>Save</button> - Arrays.
const options = ["Monthly", "Yearly"];
It cross-checks our existing database first to eliminate duplicates, automatically generating clean contextual keys (for example, converting a label named Authorise to label-authorise).
Phase 2: Automated pipeline integration
npm run upload
We hooked our script directly into the Lokalise API. The engine converts localized JSON mappings and automatically uploads them to our repository, routing around platform seat-limitation bottlenecks.
Phase 3: Automated refactoring
npm run replace
The script replaces raw strings with our generic hook (const { t } = useTranslation()). To prevent syntax breakages, we built an import-checker into the Babel traversal logic to cleanly inject the hook declaration and import statements at the top of the file only if they are missing.
4. The impact and results
Shifting from manual workflows to an AST-driven automated pipeline completely redefined our development speed and platform overhead:
- 90% reduction in cycle time. Localization overhead dropped from 4–5 hours down to under 20 minutes per feature.
- Zero value duplication. Cross-checking existing databases before key generation completely halted duplicate key creation.
- Direct cost savings. Flattening our duplicate key expansion successfully optimized our data usage, freezing our Lokalise bills into a predictable, lower-tier pricing bracket.
- Developer independence. Bypassing manual seat limits allowed developers to ship features to production smoothly without platform access blockers.
5. Key takeaways and lessons
Building a custom Babel compiler taught us that abstract syntax trees are incredibly powerful for structural automation, but they require strict guardrails. In retrospect, writing custom code-mutation logic to refactor files introduces permanent technical debt as syntax rules evolve. If we were to build it again, we would shift toward global component injections (like a global <T id="key" /> component mapped via Vite/Webpack) to completely eliminate the need for injecting explicit function imports in every single file.
How is your team handling localization bottlenecks? Have you experimented with AST code mutations or did you choose off-the-shelf frameworks?