A VS Code extension that automatically extracts hard-coded user-facing strings from your Angular source code, generates organized i18n JSON files with support for multiple locales, and automatically replaces strings in your source code with translation keys.
- 🔍 Automatic String Extraction - Scans JS/TS/HTML files for user-facing strings
- 🧭 Zero Path Configuration - Detects the Angular project from where you trigger the command (nearest
angular.json, up or down), reads the source folder from itssourceRoot, and derives every other path from it — monorepos likeprojects/app/srcwork with no settings - 📁 Consolidated Locale Output - Generates one locale JSON per language in
<sourceRoot>/assets/i18n(for exampleen-US.json,pt-BR.json) - 🔄 Source Code Transformation - Replaces strings with translation keys (
{{ 'KEY' | translate }}in HTML,this.translateService.instant('KEY')in TS) - 🧩 Auto-Import TranslateModule - Automatically adds TranslateModule to component imports arrays
- 💉 Collision-Safe TranslateService Injection - Reuses any existing injection (
inject()or constructor, any name); otherwise injectsprivate translateService = inject(TranslateService), picking a name that never collides with your class members — and repairs collisions left by older versions - 🛡️ Defensive Extraction - Never touches Angular control-flow syntax (
@if/@else/@forbraces), HTML comments, CSS selectors in DOM calls (querySelectorAll), or strings outside a class wherethis.cannot compile - 🌍 Smart Language Handling - Generate files for specific locales (en-US) or main languages (en) with automatic fallback
- 🎯 Active Language Filtering - Generate only active languages from your configuration
- 🔑 Nested Key Structure - UPPERCASE keys with underscores in hierarchical JSON format
- ✨ Auto-fill Metadata - Automatically generates language names and flags
- 🔄 Translation Preservation - Existing translations are never overwritten
- ✂️ Prune + Auto-Revert - Remove selected keys from default locale JSON and sync deletion/revert across workspace
- 🧹 Workspace Controls - Exclude paths from scan and clear extension workspace state from commands/menu
- 🚫 Smart Filtering - Automatically excludes technical strings, imports, object keys, state object values
- 📦 Custom Loader Generator - Creates optimized Angular HttpClient loader with language fallback support
- 🎨 main.ts Auto-Wiring - Automatically configures TranslateModule in your Angular bootstrap
- 🎨 Language Selector Component - Generates ready-to-use UI component for language switching
- 🔧 Translation Command Integration - Optional automatic translation after extraction
- Open VS Code
- Go to Extensions (Ctrl+Shift+X or Cmd+Shift+X)
- Search for "Angular Translation Extractor"
- Click Install
- Download the
.vsixfile - Open VS Code
- Go to Extensions
- Click the "..." menu → "Install from VSIX..."
- Select the downloaded file
- Clone this repository
- Run
npm install - Run
npm run compile - Press F5 to open a new VS Code window with the extension loaded
- VS Code: 1.85.0 or higher
- Node.js: For language metadata generation and extension runtime
- Angular Project: This extension is designed for Angular applications using
@ngx-translate/core
npm install @ngx-translate/core @ngx-translate/http-loaderThese packages are required for the generated translation loader to work in your Angular application.
Create the languages list at <sourceRoot>/app/core/json/language-code.json (e.g. src/app/core/json/language-code.json) with your target locales:
[
{
"rank": 1,
"code": "en-US",
"default": true,
"active": true
},
{
"rank": 2,
"code": "pt-BR",
"active": true
},
{
"rank": 3,
"code": "es-ES",
"active": false
}
]Field Descriptions:
code(required) - Locale code (e.g., "en-US", "pt-BR")default(optional) - Set totrueto use as default/base source languageactive(optional) - Set totrueto generate JSON for this languagerank(optional) - Sort order for language selection UIenglishName,nativeName,flag(auto-generated) - Extension will fill these automatically
There is nothing to configure for paths. When you run a command, the extension:
- Finds the Angular project that owns the file/folder you triggered from — the nearest
angular.json, searched up from the trigger and then down from the opened folder. Noangular.jsonanywhere → the command stops with a clear error. - Reads the source folder from that project's
sourceRoot(soprojects/app/srcmonorepos work untouched). - Derives every path from it: locale JSONs go to
<sourceRoot>/assets/i18n, the languages list is<sourceRoot>/app/core/json/language-code.json, andmain.tscomes from the build target inangular.json.
Behaviour (not paths) can still be customized in VS Code Settings (Ctrl+, → "Angular Translation Extractor"): minimum string length, HTML attributes to extract, aggressive mode, auto-translate, ignore globs.
You can run the extraction from multiple places:
- Command Palette:
Ctrl+Shift+P(Windows/Linux) orCmd+Shift+P(Mac)
- Type: "Angular: Extract translations"
- Explorer context menu (right-click a folder)
- Editor title menu (toolbar)
The extension will:
- Scan your source files
- Extract user-facing strings
- Generate locale JSON files
- Auto-fill language metadata
- Generate translation loader and language selector component
- Display progress in the "Angular Translation Extractor" output channel
Source file:
src/components/LoginForm.ts
Generated translations:
src/assets/i18n/
├── en-US.json
├── pt-BR.json
└── es-ES.json
Generated utilities:
src/translate/
├── tg-translate-loader.ts (Translation loader)
├── readme.md (Setup instructions)
├── language-selector.component.ts (Language selector component)
├── language-selector.component.html (Component template)
└── language-selector.component.css (Component styles)
Import the standalone component and use it in your templates. It supports both light and dark modes:
import { Component } from '@angular/core';
import { TgLanguageSelectorComponent } from './translate/tg-language-selector.component';
@Component(
{
selector:
'app-header',
standalone: true,
imports:
[
TgLanguageSelectorComponent,
],
template: `
<nav>
<!-- Default Light Mode -->
<tg-language-selector></tg-language-selector>
or
<!-- Light Mode -->
<tg-language-selector
[mode]="
'white'
"
></tg-language-selector>
<!-- Dark Mode -->
<tg-language-selector
[mode]="
'dark'
"
></tg-language-selector>
</nav>
`,
},
)
export class HeaderComponent {}Input:
const message =
'Welcome to our app';
const title = `User Profile`;Generated base locale (en-US.json):
{
"COMPONENTS": {
"LOGINFORM": {
"WELCOME_TO_OUR_APP": "Welcome to our app",
"USER_PROFILE": "User Profile"
}
}
}Input:
<button
title="Click to submit"
>
Send
</button>
<img
alt="Company logo"
src="logo.png"
/>Generated keys:
{
"COMPONENTS": {
"FORM": {
"CLICK_TO_SUBMIT": "Click to submit",
"SEND": "Send",
"COMPANY_LOGO": "Company logo"
}
}
}Paths are not settings. The Angular project root, source folder, locale output folder, languages list and main.ts are all detected from your angular.json (see Getting Started). The old rootPath, srcDir, outputRoot, languagesJsonPath and mainTsPath settings were removed — delete them from your settings.json if present; they are ignored.
Behaviour can be configured through VS Code settings (settings.json) or the Settings UI:
| Setting ID | Description | Default |
|---|---|---|
i18nExtractor.aggressiveMode |
Function-parameter extraction mode: low, moderate, high |
moderate |
i18nExtractor.aggressiveModeAllowCallRegex |
Regex allowlist for full function-call source (priority over aggressiveMode) |
[^alert\\s*\\(, ^confirm\\s*\\(, ^prompt\\s*\\(] |
i18nExtractor.aggressiveModeAllowContextRegex |
Regex allowlist for argument context (priority over aggressiveMode) |
[^window\\.alert\\(arg#1\\)$, ^window\\.confirm\\(arg#1\\)$, ^window\\.prompt\\(arg#1\\)$] |
i18nExtractor.minStringLength |
Ignore strings shorter than this | 2 |
i18nExtractor.ignoreGlobs |
Glob patterns to ignore | ["\*\*/*.test._", "\*\*/_.spec.\*", "**/node_modules/**", "**/dist/**", "**/build/**", "**/.next/**", "**/.angular/**", "**/app.html", "**/index.html", "**/assets/**", "**/environments/**", "**/.agent/**", "**/.vscode/**"] |
i18nExtractor.skipGlobs |
Additional glob patterns to skip | [] |
i18nExtractor.htmlAttributeNames |
HTML attributes to extract | ["title", "alt", "placeholder", "aria-label", "aria-placeholder"] |
i18nExtractor.angularBootstrapStyle |
standaloneormodule |
standalone |
i18nExtractor.updateMode |
Source source updates:merge, overwrite, recreate |
merge |
i18nExtractor.autoTranslate |
Automatically translate keys | true |
i18nExtractor.autoTranslateDefaultLanguage |
Translate source language (usually false) | false |
i18nExtractor.googleTranslateDelay |
Delay between translation API calls (ms) | 500 |
i18nExtractor.useTranslateCommand |
Run custom command after extration | false |
i18nExtractor.translateCommand |
Custom translation command | npx-translate |
i18nExtractor.translateArgsTemplate |
Arguments for custom command | ["--input", "{baseFile}", "--outDir", "{outDir}", "--from", "{baseLocale}", "--to", "{targetLocale}"] |
The <sourceRoot>/app/core/json/language-code.json file defines your supported languages:
[
{
"rank": 1,
"code": "en-US",
"englishName": "English (United States)",
"nativeName": "English (United States)",
"flag": "https://flagcdn.com/w40/us.png",
"default": true,
"active": true
},
{
"rank": 2,
"code": "pt-BR",
"englishName": "Portuguese (Brazil)",
"nativeName": "Português (Brasil)",
"flag": "https://flagcdn.com/w40/br.png",
"default": false,
"active": true
},
{
"rank": 3,
"code": "pt-PT",
"englishName": "Portuguese (Portugal)",
"nativeName": "Português (Portugal)",
"flag": "https://flagcdn.com/w40/pt.png",
"default": false,
"active": false
},
{
"rank": 4,
"code": "es-ES",
"englishName": "Spanish (Spain)",
"nativeName": "Español (España)",
"flag": "https://flagcdn.com/w40/es.png",
"default": false,
"active": true
},
{
"rank": 5,
"code": "fr-FR",
"englishName": "French (France)",
"nativeName": "Français (France)",
"flag": "https://flagcdn.com/w40/fr.png",
"default": false,
"active": true
},
{
"rank": 6,
"code": "it-IT",
"englishName": "Italian (Italy)",
"nativeName": "Italiano (Italia)",
"flag": "https://flagcdn.com/w40/it.png",
"default": false,
"active": true
},
{
"rank": 7,
"code": "zh-CN",
"englishName": "Chinese (Simplified, China)",
"nativeName": "中文 (中国)",
"flag": "https://flagcdn.com/w40/cn.png",
"default": false,
"active": true
}
]Note: If this file doesn't exist, the extension will automatically create it with a default set of languages. Properties like englishName, nativeName, and flag are auto-filled if missing.
The extension replicates your source folder structure for organized locale files:
Source structure:
src/
├── components/
│ ├── LoginForm.ts
│ └── Header.html
└── pages/
└── Home.js
Generated output:
src/assets/I18n/
├── components/
│ ├── LoginForm/
│ │ ├── en-US.json
│ │ ├── pt-BR.json
│ │ └── es-ES.json
│ └── Header/
│ ├── en-US.json
│ ├── pt-BR.json
│ └── es-ES.json
└── pages/
└── Home/
├── en-US.json
├── pt-BR.json
└── es-ES.json
Every extraction run also generates a translation loader inside your source directory:
src/translate/tg-translate-loader.tssrc/translate/readme.md
This loader is configured to load language files from your output directory.
The generated loader includes intelligent language fallback:
- Loads files named exactly as requested (e.g.,
en-US.json,pt-BR.json)
This allows you to generate fewer files while supporting multiple regional variants:
// User selects "en-US" in the app
translateService.use(
'en-US',
);
// Loader tries "en-US.json" first
// Falls back to "en.json" if not foundQuick usage:
import { HttpClient } from '@angular/common/http';
import { TranslateLoader } from '@ngx-translate/core';
import { TgTranslationLoader } from './translate/tg-translate-loader';
export function HttpLoaderFactory(
http: HttpClient,
): TranslateLoader {
// The path is derived from your Angular project's sourceRoot
return new TgTranslationLoader(
http,
'./assets/I18n/',
);
}The loader path is derived automatically from your Angular project's sourceRoot (locales are served from ./assets/i18n/). If you need a custom path, you can modify the prefix parameter. The auto-generated translate/readme.md file includes the full configuration details.
Every extraction run also generates a ready-to-use Language Selector Component that provides a dropdown UI for switching languages:
Generated files:
src/translate/tg-language-selector.component.ts- Anguar standalone componentsrc/translate/tg-language-selector.component.html- Template with dropdown UIsrc/translate/tg-language-selector.component.css- Professional styles with dark mode support
- 🎨 Professional UI - Modern dropdown with flags, language names, and smooth animations
- 🌓 Dark Mode Support - Automatically adapts to user's color scheme preference
- 🔄 Auto-Configuration - Reads languages from your i18n-languages.json
- 💾 Persistent Selection - Saves user's language choice to localStorage
- 🌍 Language Metadata - Shows native names, English names, and flags (if configured)
- ✨ Standalone Component - No module imports needed, works with modern Angular
The component is generated as a standalone component with the selector tg-language-selector. You can import and use it directly:
// In your app component or any standalone component
import { Component } from '@angular/core';
import { TgLanguageSelectorComponent } from './translate/tg-language-selector.component';
@Component(
{
selector:
'app-root',
standalone: true,
imports:
[
TgLanguageSelectorComponent,
],
template: `
<header>
<h1>
My
App
</h1>
<tg-language-selector></tg-language-selector>
</header>
<router-outlet></router-outlet>
`,
},
)
export class AppComponent {}The component uses CSS variables for easy customization:
/* In your global styles.css or component styles */
.language-selector {
--background: #ffffff;
--hover-background: #f5f5f5;
--border-color: #e0e0e0;
--text-color: #333333;
--selected-background: #e3f2fd;
--selected-text-color: #1976d2;
}For best results, ensure your i18n-languages.json includes display metadata:
[
{
"code": "en-US",
"englishName": "English (United States)",
"nativeName": "English",
"flag": "🇺🇸",
"default": true,
"active": true
},
{
"code": "pt-BR",
"englishName": "Portuguese (Brazil)",
"nativeName": "Português",
"flag": "🇧🇷",
"active": true
}
]The component will auto-generate basic labels if metadata is missing, but providing full details creates a better user experience.
Access settings via: File → Preferences → Settings (or Ctrl+,) → Search for "Angular Translation Extractor"
Paths are detected, not configured — project root from the nearest
angular.json, source folder from itssourceRoot, locales at<sourceRoot>/assets/i18n, languages list at<sourceRoot>/app/core/json/language-code.json,main.tsfrom the build target.
| Setting | Default | Description |
|---|---|---|
i18nExtractor.aggressiveMode |
"moderate" |
Function-parameter extraction mode: "low" blocks all, "moderate" allows multi-word, "high" allows all |
i18nExtractor.aggressiveModeAllowCallRegex |
[^alert\\s*\\(, ^confirm\\s*\\(, ^prompt\\s*\\(] |
Regex allowlist matched against full call source (example: alert('teste')). Matches have priority over aggressiveMode |
i18nExtractor.aggressiveModeAllowContextRegex |
[^window\\.alert\\(arg#1\\)$, ^window\\.confirm\\(arg#1\\)$, ^window\\.prompt\\(arg#1\\)$] |
Regex allowlist matched against function argument context (example: this.toastr.error(arg#1)). Matches have priority over aggressiveMode |
i18nExtractor.minStringLength |
2 |
Ignore strings shorter than this length |
i18nExtractor.updateMode |
"merge" |
Controls JSON file updates:"merge"(preserve translations, add new keys),"overwrite"(recreate non-default languages),"recreate"(recreate all including default) |
i18nExtractor.angularBootstrapStyle |
"standalone" |
How to wire TranslateModule in main.ts (standaloneormodule) |
i18nExtractor.updateMainTs |
true |
If true, update main.ts to wire the translation loader |
| Setting | Default | Description |
|---|
Language Generation Examples:
- Generates:
en-US.json,en-GB.json,pt-BR.json,es-ES.json
| Setting | Default | Description |
|---|---|---|
i18nExtractor.ignoreGlobs |
["**/*.test.*", "**/*.spec.*", ...] |
Glob patterns to exclude from scanning |
i18nExtractor.skipGlobs |
[] |
Additional glob patterns to exclude from scanning (merged with ignoreGlobs) |
i18nExtractor.htmlAttributeNames |
["title", "alt", "placeholder", ...] |
HTML attribute names to extract values from |
Default ignored patterns:
**/*.test.*- Test files**/*.spec.*- Spec files**/node_modules/**- Dependencies**/dist/**,**/build/**- Build outputs**/.next/**- Next.js build cache**/main.ts- Angular main bootstrap file (hardcoded exclusion from extraction)
Default HTML attributes:
title- Element tooltipsalt- Image alt textplaceholder- Input placeholdersaria-label- Accessibility labelsaria-placeholder- Accessibility placeholders
The updateMode setting controls how JSON translation files and generated artifacts are updated:
| Mode | Default Language | Non-Default Languages | Generated Files (loader, readme, etc.) |
|---|---|---|---|
| merge (default) | Merges with existing, preserves manual translations | Preserves translations, adds new keys as blank | Only writes if file doesn't exist |
| overwrite | Merges with existing, preserves manual translations | Recreates with all keys as blank | Overwrites existing files |
| recreate | Recreates, loses manual edits | Recreates with all keys as blank | Overwrites existing files |
When to use each mode:
-
merge (Recommended for most workflows):
- Preserves your manual translations
- Only adds new extracted strings as blank entries
- Safe for iterative development
- Generated files (loader, readme) are only created if they don't exist
-
overwrite:
- Use when you want to reset all non-default language translations
- Keeps your default language (source) translations
- Forces regeneration of all generated files
- Useful after major restructuring
-
recreate:
- Complete reset of everything
- Use only when you want to start completely fresh
- All translations including default language are reset
- All generated files are recreated
Configuration:
{
"i18nExtractor.updateMode": "merge" // or "overwrite" or "recreate"
}The extension supports automatic translation using free public translation APIs - no API key required!
| Service | Quality | Speed | API Key | Rate Limit | Recommended |
|---|---|---|---|---|---|
| Google Translate (Default) | ⭐⭐⭐⭐⭐ Excellent | Fast | No | Yes (managed) | ✅ Yes |
Features:
- Automatic translation runs after string extraction
- Translates to all configured target languages
- Preserves manually edited translations
- Can be disabled per run or globally in settings
- Works offline after initial translation
| Setting | Default | Description |
|---|---|---|
i18nExtractor.autoTranslate |
true |
If true, automatically initiate translations after extracting base locale strings |
i18nExtractor.autoTranslateDefaultLanguage |
false |
If false, skip translating the default language (marked with "default": true). The default language is the translation source. |
i18nExtractor.googleTranslateDelay |
500 |
Delay between translation requests in ms (minimum 100) |
Set autoTranslate to false:
{
"i18nExtractor.autoTranslate": false
}You can also use a custom translation command/service:
| Setting | Default | Description |
|---|---|---|
i18nExtractor.useTranslateCommand |
false |
Enable automatic translation using a custom command |
i18nExtractor.translateCommand |
"npx-translate" |
Command to run for translation |
i18nExtractor.translateArgsTemplate |
[...] |
Arguments template with placeholders |
Placeholder variables for translation command:
{baseFile}- Path to base locale JSON file{outDir}- Output directory for the file{baseLocale}- Base locale code (e.g.,"en-US"){targetLocale}- Target locale code (e.g.,"pt-BR")
Example configuration:
{
"i18nExtractor.useTranslateCommand": true,
"i18nExtractor.translateCommand": "npx",
"i18nExtractor.translateArgsTemplate": [
"translate-json",
"--input",
"{baseFile}",
"--from",
"{baseLocale}",
"--to",
"{targetLocale}",
"--output-dir",
"{outDir}"
]
}Create scripts/translate-google.js:
const fs = require('fs');
const path = require('path');
const axios = require('axios');
const OUTPUT_ROOT =
'src/assets/I18n';
const BASE_LANG =
'en';
async function translateText(
text,
targetLang,
) {
try {
const url = `https://translate.google.com/translate_a/single?client=gtx&sl=auto&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}`;
const response =
await axios.get(
url,
{
headers:
{
'User-Agent':
'Mozilla/5.0',
},
},
);
return (
response.data[0]
?.map(
(
item,
) =>
item[0],
)
.join(
'',
) ||
text
);
} catch (err) {
console.error(
`Translation failed for "${text}": ${err.message}`,
);
return text;
}
}
async function translateFile(
inputFile,
outputDir,
targetLang,
) {
console.log(
`Translating to ${targetLang}...`,
);
const data =
JSON.parse(
fs.readFileSync(
inputFile,
'utf-8',
),
);
async function translateObj(
obj,
) {
for (const [
key,
value,
] of Object.entries(
obj,
)) {
if (
typeof value ===
'string' &&
value.trim()
.length >
0
) {
obj[
key
] =
await translateText(
value,
targetLang,
);
} else if (
typeof value ===
'object' &&
value !==
null &&
!Array.isArray(
value,
)
) {
await translateObj(
value,
);
}
}
}
await translateObj(
data,
);
const outputFile =
path.join(
outputDir,
`${targetLang}.json`,
);
fs.writeFileSync(
outputFile,
JSON.stringify(
data,
null,
2,
) +
'\n',
);
console.log(
` ✓ Saved: ${outputFile}`,
);
}
(async () => {
try {
const glob = require('glob');
const baseFiles =
glob.sync(
path.join(
OUTPUT_ROOT,
`**/${BASE_LANG}.json`,
),
);
console.log(
`Found ${baseFiles.length} base files\n`,
);
for (const baseFile of baseFiles) {
const outDir =
path.dirname(
baseFile,
);
const targetLangs =
[
'pt',
'es',
'fr',
]; // Customize as needed
for (const lang of targetLangs) {
await translateFile(
baseFile,
outDir,
lang,
);
await new Promise(
(
r,
) =>
setTimeout(
r,
500,
),
); // Rate limiting
}
}
console.log(
'\n✓ Translation completed!',
);
} catch (error) {
console.error(
'Error:',
error.message,
);
process.exit(
1,
);
}
})();The languages JSON file should contain an array of locale entries. Only code is required; other fields will be auto-generated:
[
{
"rank": 1,
"code": "en-US",
"englishName": "English (United States)",
"nativeName": "English (United States)",
"flag": "https://flagcdn.com/w40/us.png",
"default": true,
"active": true
},
{
"rank": 2,
"code": "pt-BR",
"englishName": "Portuguese (Brazil)",
"nativeName": "português (Brasil)",
"flag": "https://flagcdn.com/w40/br.png",
"active": true
},
{
"rank": 3,
"code": "es-ES",
"englishName": "Spanish (Spain)",
"nativeName": "Español",
"flag": "https://flagcdn.com/w40/es.png",
"active": false
}
]Field Descriptions:
| Field | Required | Type | Auto-generated | Description |
|---|---|---|---|---|
code |
✅ Yes | string | ❌ No | Locale code (e.g., "en-US", "pt-BR") |
rank |
❌ No | number | ❌ No | Sort order (lower ranks first) |
englishName |
❌ No | string | ✅ Yes | English display name |
nativeName |
❌ No | string | ✅ Yes | Native display name |
flag |
❌ No | string | ✅ Yes | Flag emoji or icon URL |
default |
❌ No | boolean | ❌ No | If true, use as default/base source language |
After extraction, the extension automatically replaces extracted strings in your source files with translation keys:
HTML Files: Strings are replaced with Angular translate pipe syntax
<!-- Before -->
<button>
Submit
</button>
<p>
Welcome
to
our
app
</p>
<!-- After -->
<button>
{{
'COMPONENTS.FORM.SUBMIT'
|
translate
}}
</button>
<p>
{{
'COMPONENTS.FORM.WELCOME_TO_OUR_APP'
|
translate
}}
</p>TypeScript/JavaScript Files: Strings are replaced with TranslateService calls
// Before
const message =
'Welcome to our app';
const title = `User Profile`;
// After
const message =
this.translateService.translate(
'COMPONENTS.PROFILE.WELCOME_TO_OUR_APP',
);
const title =
this.translateService.translate(
'COMPONENTS.PROFILE.USER_PROFILE',
);Automatic TranslateModule Import: For components with HTML templates, the extension automatically:
- Adds
import { TranslateModule } from '@ngx-translate/core'; - Adds
TranslateModuleto the component'simportsarray (for standalone components)
Automatic TranslateService Injection: For TypeScript files with inline string replacements:
- Adds
import { TranslateService } from '@ngx-translate/core'; - Injects
TranslateServiceinto the component constructor - Example:
constructor(private translateService: TranslateService) {}
- Keys are automatically namespaced by file path in nested object structure
- Example:
COMPONENTS.LOGINFORM.WELCOME_MESSAGE - Keys use UPPERCASE with underscores for word separation
- Keys are stable across multiple runs
- Collision handling with automatic numeric suffixes
The extension uses advanced AST analysis to extract:
- ✅ String literals in explicit display context:
"Hello World" - ✅ Template literals (no expressions):
`Welcome` - ✅ HTML text content:
<p>Content</p> - ✅ HTML attribute values (configurable)
- ✅ Explicit alerts/messages:
alert("Message"),confirm("Sure?")
The extension ignores (Strict Filtering):
- ❌ Console logs:
console.log(...),console.error(...),console.warn(...) - ❌ Decorators:
@Componentselectors,@Injectable,@Pipe,@Directivemetadata - ❌ Logic: Strings in
if,for,while,switchstatements - ❌ Import/export paths:
import x from "./file" - ❌ Object keys:
{ name: "value" } - ❌ State object literals: Values in objects named
*Stateor assigned to*statevariables - ❌ URLs and paths:
"https://...","/path/to/file" - ❌ Tokens/IDs:
"ABC123XYZ" - ❌ Template expressions:
"Hello ${name}" - ❌ Placeholder syntax:
"{{variable}}","${variable}" - ❌ Pure numbers or punctuation
- ❌ Hex codes:
"3f4a5b"
State Object Filtering Example:
// ❌ These strings are NOT extracted (state configuration data)
const initialState: WizardState =
{
currentStep: 0,
templateExtension:
'pdf', // ← Not extracted
status:
'pending', // ← Not extracted
};
// ✅ These strings ARE extracted (user-facing text)
const message =
'Welcome to the wizard';
const label =
'Template Extension';Identical strings within the same source file share the same key:
Input:
const btn1 =
'Submit';
const btn2 =
'Submit';
const btn3 =
'Cancel';Output:
{
"COMPONENTS": {
"FORM": {
"SUBMIT": "Submit",
"CANCEL": "Cancel"
}
}
}Existing translations are never overwritten:
Existing pt-BR.json:
{
"COMPONENTS": {
"FORM": {
"SUBMIT": "Enviar"
}
}
}After re-running with new strings:
{
"COMPONENTS": {
"FORM": {
"SUBMIT": "Enviar",
"CANCEL": ""
}
}
}New keys get empty values, existing translations are preserved.
The extension automatically completes language information using Node.js Intl API:
Your input:
[
{
"rank": 1,
"code": "pt-BR"
}
]Auto-generated output:
[
{
"rank": 1,
"code": "pt-BR",
"englishName": "Portuguese (Brazil)",
"nativeName": "português (Brasil)",
"flag": "https://flagcdn.com/w40/br.png"
}
]Run the extraction after adding new UI text to keep your locale files up to date.
Check the base locale JSON files to ensure extracted strings are appropriate for translation.
Commit both base and target locale files. Translators can work with the target locale files directly.
Right-click a single folder and use Extract translations (File) / run on a subfolder first, then run Extract translations (All app) for the full source tree.
Add patterns to ignoreGlobs for any folders or files you don't want scanned.
- Check the Output panel (View → Output) and select "Angular Translation Extractor"
- Verify the command was triggered from inside your Angular project (the output panel logs the detected project root and source folder)
- Check if files are excluded by
ignoreGlobspatterns - Increase
minStringLengthif needed
- Adjust
minStringLengthto filter out short strings - Review
htmlAttributeNamesif HTML extraction is too broad - The heuristics aim to skip technical strings, but may not be perfect
- Ensure the languages JSON file exists at the configured path
- Check that
codefield is present for each entry - Verify the locale code format (e.g.,
"en-US","pt-BR")
If replacements occur in the wrong location or fail silently:
- The extension validates that the text at the calculated position matches exactly before replacing.
- Check the "Angular Translation Extractor" output channel for warnings about mismatches.
- Ensure your files are saved with UTF-8 encoding.
Each source file gets its own folder with separate JSON files per locale:
Base locale (en-US.json) - Contains extracted English strings:
{
"COMPONENTS": {
"HEADER": {
"WELCOME": "Welcome",
"LOGOUT": "Logout"
}
}
}Target locales (pt-BR.json, es-ES.json) - Start with empty values:
{
"COMPONENTS": {
"HEADER": {
"WELCOME": "",
"LOGOUT": ""
}
}
}Translators fill in the empty values, or use the optional translation command integration.
The extension uses a hierarchical nested object structure for better organization:
- Namespace: Based on the file path (e.g.,
COMPONENTS,PAGES,SERVICES) - Component: Based on the filename (e.g.,
LOGINFORM,HEADER,SIDEBAR) - Key: Based on the string content (e.g.,
WELCOME_MESSAGE,SUBMIT_BUTTON)
- All keys are UPPERCASE
- Words are separated by underscores (
_) - Special characters are removed or converted to underscores
- Keys are limited to 60 characters for readability
File: src/components/LoginForm.ts
String: "Welcome to our app"
Generated Key Path: COMPONENTS.LOGINFORM.WELCOME_TO_OUR_APP
If two different strings would generate the same key, the extension automatically appends a numeric suffix:
{
"COMPONENTS": {
"FORM": {
"TEXT": "First text",
"TEXT_2": "Another text"
}
}
}| Command | ID | Description |
|---|---|---|
| Extract translations (All app) | angularTranslation.extract |
Scan full source, generate/sync locale files, replace source strings |
| Extract translations (File) | angularTranslation.extractFile |
Extract and replace strings for one file |
| Extract translations (Selection) | angularTranslation.extractSelection |
Extract selected text and replace with translate.instant(...) / pipe |
| Extract Selection into ( 'KEY' | translate ) | angularTranslation.extractSelectionParenthesis |
Extract selected expression into parenthesized pipe form |
| Reverse Translate (Folder) | angularTranslation.reverseFromFolder |
Revert translated keys back to source strings in a folder |
| Reverse Translate (File) | angularTranslation.reverseFromFile |
Revert translated keys in a single file |
| Reverse Translate (Selection) | angularTranslation.reverseSelection |
Revert translated keys only in selected range |
| Prune Selected Translation Keys (JSON) | angularTranslation.pruneSelectedKeys |
Remove selected keys from locale files and auto-revert usages |
| Exclude from Workspace Scan | angularTranslation.excludeWorkspacePath |
Add selected file/folder to i18nExtractor.skipGlobs |
| Clear i18n Workspace State | angularTranslation.clearWorkspaceState |
Clear extension workspace state with confirmation |
| Insert Language Selector | angularTranslation.insertSelector |
Insert selector component tag in HTML and wire import |
- VS Code 1.85.0 or higher
- Node.js (for language metadata generation)
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Author: Adilson de Almeida Pedro
Website: https://adilson.almeidapedro.com.br
GitHub: @devremoto
Twitter: @devremoto
LinkedIn: Adilson Pedro
This project is licensed under the MIT License - see the LICENSE file for details.
If you encounter any issues or have questions:
- Check the Known Limitations section
- Search existing issues
- Create a new issue with detailed reproduction steps
- Thanks to the Angular team for creating an amazing framework
- Inspired by the need for better developer experience in Angular projects
- Built with love for the Angular community by Adilson de Almeida Pedro
I build and maintain open-source projects to help the developer community.
If you find my work useful, please consider supporting future development! 🙌
Enjoy coding with Angular Translation Extractor! 🎉
Created by Adilson de Almeida Pedro - Full Stack Developer
Happy internationalizing! 🌍
