vuei18n-extractor
v2.4.0
Published
Automated translation key extractor for vue-i18n with composition API support
Maintainers
Readme
vuei18n-extractor
🌍 Automated translation key extractor for vue-i18n with Composition API support
Extrator automatizado de chaves de tradução para vue-i18n, projetado para simplificar a internacionalização de aplicações Vue.js.
✨ Features
- 🚀 Extração automática de chaves
t()de arquivos Vue e JavaScript - 🌐 Suporte a múltiplos idiomas simultaneamente
- 📦 Múltiplos formatos de saída (JS, JSON, TypeScript)
- 🔄 Preserva traduções existentes
- 📝 Agrupa chaves por arquivo de origem
- ⚙️ Configuração flexível (JS ou JSON)
- 🎯 Zero dependências de runtime
- ✨ NOVO: Suporte a interpolação de variáveis
{name} - ✨ NOVO: Suporte a pluralização (ICU MessageFormat)
{count, plural, ...} - ✨ NOVO: Suporte a formatação de datas
{date, date, short} - ✨ NOVO: Arquivo
index.js/tsgerado automaticamente para fácil import - ✨ NOVO: Divisão inteligente em múltiplos arquivos (feature splitting)
- 🔄 NOVO: Migração automática preservando traduções (resolve arquivos com
[id],[slug]) - 🔒 NOVO: Segurança robusta contra injection attacks
📦 Instalação
npm install -g vuei18n-extractorOu use diretamente com npx:
npx vuei18n-extractor🚀 Uso Rápido
1. Crie o arquivo de configuração
Crie um arquivo i18nExtractor.js ou i18nExtractor.json na raiz do projeto:
1. Crie o arquivo de configuração
Crie um arquivo i18nExtractor.js ou i18nExtractor.json na raiz do projeto:
Opção 1: JavaScript (recomendado)
// i18nExtractor.js
module.exports = {
header: "export default", // ou "module.exports=" para CommonJS
sourceLocale: "pt", // idioma fonte
locales: ["pt", "en", "es"], // todos os idiomas
format: "js", // formato de saída: "js", "json" ou "ts"
catalogs: {
outputFolder: "src/locales", // onde salvar os arquivos
include: ["src/**/*.{vue,js,ts}"], // arquivos para escanear
exclude: ["src/locales/*"] // arquivos para ignorar
}
};Opção 2: JSON
{
"sourceLocale": "en",
"locales": ["en", "fr", "de"],
"format": "json",
"catalogs": {
"outputFolder": "locales",
"include": ["src/**/*.vue"],
"exclude": []
}
}2. Use t() no seu código
Tradução simples:
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const greeting = t("Hello World");
</script>
<template>
<h1>{{ t("Welcome to my app") }}</h1>
</template>Com interpolação de variáveis:
<script setup>
const welcome = t("Hello {name}, welcome back!");
const message = t("You have {count} new messages");
</script>
<template>
<p>{{ t("Welcome, {username}!") }}</p>
</template>Com pluralização (ICU MessageFormat):
<script setup>
// Pluralização automática baseada no count
const items = t("{count, plural, =0 {no items} one {# item} other {# items}}");
const notifications = t("{n, plural, zero {no notifications} one {# notification} other {# notifications}}");
</script>
<template>
<span>{{ t("{count, plural, one {# message} other {# messages}}") }}</span>
</template>Com formatação de data:
<script setup>
const today = t("Today is {date, date, long}");
const time = t("Current time: {now, time, short}");
</script>Exemplo complexo:
<script setup>
const complex = t("Hello {name}, you have {count, plural, zero {no messages} one {# message} other {# messages}} from {date, date, short}");
</script>3. Execute o extrator
npx vuei18n-extractor4. Resultado
O extrator gera arquivos de tradução automaticamente com metadados úteis:
src/locales/pt.js (idioma fonte)
export default {
/*
src/Component.vue
*/
// Variables: name
"Hello {name}, welcome back!": "Hello {name}, welcome back!",
// Uses pluralization
"{count, plural, one {# item} other {# items}}": "{count, plural, one {# item} other {# items}}",
// Uses date formatting
"Today is {date, date, long}": "Today is {date, date, long}",
"Welcome to my app": "Welcome to my app",
};src/locales/en.js (outros idiomas - com metadados para facilitar tradução)
export default {
/*
src/Component.vue
*/
// Variables: name
"Hello {name}, welcome back!": "",
// Uses pluralization
"{count, plural, one {# item} other {# items}}": "",
// Uses date formatting
"Today is {date, date, long}": "",
"Welcome to my app": "",
};src/locales/index.js (✨ NOVO - gerado automaticamente!)
import pt from './pt.js';
import en from './en.js';
export const messages = {
'pt': pt,
'en': en
};
export default messages;5. Configure Vue i18n
// src/i18n.ts
import { createI18n } from 'vue-i18n';
import pt from './locales/pt';
import en from './locales/en';
export default createI18n({
locale: 'pt',
fallbackLocale: 'en',
messages: { pt, en },
});Com splitting ativo, cada arquivo de locale (pt.js) importa automaticamente todos seus namespaces!
⚙️ Opções de Configuração
| Opção | Tipo | Obrigatório | Padrão | Descrição |
|-------|------|-------------|--------|-----------|
| sourceLocale | string | ✅ | - | Idioma fonte do projeto |
| locales | string[] | ✅ | - | Lista de todos os idiomas suportados |
| format | "js" \| "json" \| "ts" | ✅ | - | Formato dos arquivos de saída |
| header | string | ❌ | "module.exports=" | Cabeçalho dos arquivos gerados |
| catalogs.outputFolder | string | ✅ | - | Diretório para salvar arquivos |
| catalogs.include | string[] | ✅ | - | Padrões glob de arquivos para escanear |
| catalogs.exclude | string[] | ❌ | [] | Padrões glob de arquivos para ignorar |
| splitting | object | ❌ | - | Configuração para dividir traduções em múltiplos arquivos |
📂 Dividindo Traduções em Múltiplos Arquivos
Para projetos grandes, você pode dividir automaticamente as traduções em múltiplos arquivos baseado na estrutura do projeto. Você continua escrevendo mensagens em linguagem natural, o splitting é transparente!
Estratégias Disponíveis
1. Flat (Padrão) - Todas traduções em um único arquivo
// Sem configuração de splitting2. Directory - Baseado na estrutura de diretórios
{
splitting: {
strategy: "directory",
maxDepth: 2
}
}
// src/pages/auth/Login.vue → namespace: "pages.auth"
// Gera: pt.pages.auth.js, en.pages.auth.js3. Feature - Baseado em pastas de features
{
splitting: {
strategy: "feature",
featureFolders: ["features", "modules"]
}
}
// src/features/auth/Login.vue → namespace: "auth"
// Gera: pt.auth.js, en.auth.js4. Custom - Função personalizada
{
splitting: {
strategy: "custom",
customNamespace: (filePath, baseDir) => {
if (filePath.includes('/admin/')) return 'admin';
if (filePath.includes('/public/')) return 'public';
return 'common';
}
}
}📚 Documentação completa sobre Splitting →
🔄 Migração Automática de Arquivos
Problema resolvido: Se você tinha arquivos com nomes inválidos (ex: pt-BR.pages.employees.[id].js) devido ao uso de rotas dinâmicas do Vue Router, não é mais necessário deletar e traduzir tudo novamente! 🎉
Como Funciona
O extrator detecta automaticamente arquivos com caracteres inválidos ([, ], (, ), {, }, <, >) e:
- ✅ Renomeia para o formato sanitizado
- ✅ Preserva todas as traduções existentes
- ✅ Mescla duplicados se necessário (novo tem prioridade)
- ✅ Funciona com JS, TS e JSON
- ✅ Zero configuração necessária
Exemplos de Migração
# Antes da migração (nomes inválidos)
src/locales/
├── pt-BR.pages.employees.[id].js
├── en.pages.products.[slug].ts
└── es-ES.components.(group).items.[customId].json
# Depois da migração automática (nomes válidos)
src/locales/
├── pt-BR.pages.employees.id.js ✅ Traduções preservadas
├── en.pages.products.slug.ts ✅ Traduções preservadas
└── es-ES.components.group.items.param.json ✅ Traduções preservadasConversões Aplicadas
| Padrão Original | Convertido Para | Exemplo |
|----------------|-----------------|---------|
| [id] | id | pages.users.[id] → pages.users.id |
| [slug] | slug | posts.[slug] → posts.slug |
| [qualquercoisa] | param | items.[customId] → items.param |
| (grupo) | grupo | pages.(admin) → pages.admin |
| Múltiplos | Combinado | pages.[id].edit.[tab] → pages.id.edit.param |
Mesclagem de Duplicados
Se você tiver ambos os arquivos (antigo e novo):
// pt-BR.pages.users.[id].js (arquivo antigo com traduções)
export default {
"title": "Perfil do Usuário",
"edit": "Editar",
"shared": "Do arquivo antigo"
};
// pt-BR.pages.users.id.js (arquivo novo já existente)
export default {
"name": "Nome",
"shared": "Do arquivo novo"
};
// Resultado após migração (mescla inteligente)
export default {
"title": "Perfil do Usuário", // Do antigo
"edit": "Editar", // Do antigo
"name": "Nome", // Do novo
"shared": "Do arquivo novo" // Novo tem prioridade
};Quando Acontece
A migração é executada automaticamente antes de cada extração:
npx vuei18n-extractor
# Output:
# 📦 Migrated pt-BR.pages.employees.[id].js → pt-BR.pages.employees.id.js
# 📦 Migrated en.pages.products.[slug].ts → en.pages.products.slug.ts
# 🔀 Merged es.pages.items.[id].js → es.pages.items.id.js (duplicate resolved)
# ✓ Migrated 3 file(s) to sanitized names
#
# 📂 Scanning 45 file(s)...
# 🔑 Found 234 unique key(s)
# ...Benefícios:
- ✅ Sem perda de traduções
- ✅ Sem trabalho manual
- ✅ Funciona com qualquer quantidade de arquivos
- ✅ Resolve conflitos automaticamente
- ✅ Idempotente (pode executar múltiplas vezes sem problemas)
📚 Documentação completa sobre Splitting →
📖 Exemplos
Exemplo com TypeScript
// i18nExtractor.js
module.exports = {
header: "export default",
sourceLocale: "en",
locales: ["en", "ja", "ko"],
format: "ts",
catalogs: {
outputFolder: "src/i18n/locales",
include: ["src/**/*.{vue,ts,tsx}"],
exclude: ["src/**/*.spec.ts", "src/i18n/*"]
}
};Exemplo com JSON
// i18nExtractor.js
module.exports = {
sourceLocale: "pt",
locales: ["pt", "en"],
format: "json",
catalogs: {
outputFolder: "public/locales",
include: ["src/components/**/*.vue", "src/views/**/*.vue"],
exclude: []
}
};🔍 Como Funciona
- Escaneamento: Busca todos os arquivos que correspondem aos padrões
include - Extração: Encontra todas as chamadas
t("key")out('key')no código - Agrupamento: Organiza chaves por arquivo de origem
- Geração: Cria/atualiza arquivos de locale preservando traduções existentes
- Limpeza: Remove chaves que não existem mais no código
⚠️ Limitações
❌ Não Suportado (ainda)
- Chaves dinâmicas:
t(variableName) - Chamadas com expressões:
t("key" + suffix)
✅ Suportado
- Strings literais:
t("key")✅ - Interpolação:
t("Hello {name}")✅ - Pluralização ICU:
t("{count, plural, one {# item} other {# items}}")✅ - Formatação de data:
t("{date, date, short}")✅ - Template literals:
t(`Hello {name}`)✅ - Múltiplas linhas (dentro de strings) ✅
🔒 Segurança
Esta biblioteca foi desenvolvida com segurança em mente:
- ✅ Proteção contra Path Traversal: Todos os caminhos são validados
- ✅ Proteção contra Code Injection: Strings são escapadas adequadamente
- ✅ Validação de Configuração: Schema estrito com whitelist
- ✅ Proteção contra ReDoS: Regex com repetição limitada
- ✅ Sanitização de Variáveis: Apenas nomes válidos são aceitos
- ✅ Validação de ICU MessageFormat: Sintaxe validada antes de processar
Exemplos de Proteção
// ❌ Path traversal bloqueado
{
"catalogs": {
"outputFolder": "../../../etc" // ERRO: Path traversal detectado
}
}
// ❌ Locale inválido bloqueado
{
"locales": ["en; rm -rf /"] // ERRO: Caracteres inválidos
}
// ❌ Chaves maliciosas são escapadas
t('test"; maliciousCode(); "') // Escapado automaticamente🛠️ Desenvolvimento
Pré-requisitos
- Node.js >= 18.0.0
- npm ou yarn
Setup do Projeto
# Clone o repositório
git clone https://github.com/abraaobuenotype/vuei18n-extractor.git
cd vuei18n-extractor
# Instale dependências
npm installScripts Disponíveis
# Executar testes
npm test
# Testes em modo watch
npm run test:watch
# Cobertura de testes
npm run test:coverage
# Verificar código (linting)
npm run lint
# Corrigir problemas de lint
npm run lint:fix
# Formatar código
npm run format
# Verificar formatação
npm run format:checkRodando os Testes
npm test15 testes unitários e de integração garantem a qualidade do código.
🤝 Contribuindo
Contribuições são bem-vindas! Sinta-se à vontade para:
- Fazer fork do projeto
- Criar uma branch para sua feature (
git checkout -b feature/MinhaFeature) - Commit suas mudanças (
git commit -m 'Adiciona MinhaFeature') - Push para a branch (
git push origin feature/MinhaFeature) - Abrir um Pull Request
Por favor, certifique-se de:
- ✅ Adicionar testes para novas funcionalidades
- ✅ Executar
npm run lintenpm run format - ✅ Todos os testes passando (
npm test)
📝 Changelog
Veja CHANGELOG.md para histórico de versões e mudanças.
📄 Licença
MIT © Abraao Bueno
💡 Inspiração
Inspirado na biblioteca lingui para React.
🙏 Agradecimentos
Obrigado a todos os contribuidores que ajudaram a melhorar este projeto!
