255 lines
15 KiB
TypeScript
255 lines
15 KiB
TypeScript
/**
|
|
* i18n completo — M9.8
|
|
*
|
|
* Dicionário pt-BR + en-US para todas as strings de UI do VentoApp.
|
|
*
|
|
* Convenção:
|
|
* - Chaves em snake_case agrupadas por área (nav_*, app_*, common_*, etc.)
|
|
* - Fallback automático: chave → en-US → pt-BR
|
|
* - Interpolação via {placeholder} (substituição simples)
|
|
* - Persistência em localStorage com chave 'ventoapp.locale'
|
|
*/
|
|
|
|
export type Locale = 'pt-BR' | 'en-US';
|
|
|
|
export const supportedLocales: readonly Locale[] = ['pt-BR', 'en-US'] as const;
|
|
export const DEFAULT_LOCALE: Locale = 'pt-BR';
|
|
const LOCALE_STORAGE_KEY = 'ventoapp.locale';
|
|
|
|
/** Carrega locale do localStorage ou retorna o padrão. */
|
|
export function loadStoredLocale(): Locale {
|
|
if (typeof window === 'undefined') return DEFAULT_LOCALE;
|
|
try {
|
|
const stored = window.localStorage.getItem(LOCALE_STORAGE_KEY);
|
|
if (stored === 'pt-BR' || stored === 'en-US') return stored;
|
|
} catch {
|
|
// localStorage indisponível (modo privado, etc.) — usa padrão
|
|
}
|
|
return DEFAULT_LOCALE;
|
|
}
|
|
|
|
/** Persiste locale no localStorage. */
|
|
export function saveStoredLocale(locale: Locale): void {
|
|
if (typeof window === 'undefined') return;
|
|
try {
|
|
window.localStorage.setItem(LOCALE_STORAGE_KEY, locale);
|
|
} catch {
|
|
// localStorage indisponível — silenciosamente ignora
|
|
}
|
|
}
|
|
|
|
/** Dicionário principal de traduções. */
|
|
type Dict = Record<string, Record<Locale, string>>;
|
|
|
|
const translations: Dict = {
|
|
// === Aplicação ===
|
|
app_title: { 'pt-BR': 'BrainWind v1.0', 'en-US': 'BrainWind v1.0' },
|
|
app_subtitle: { 'pt-BR': 'Cálculo de cargas de vento — NBR 6123:2023', 'en-US': 'Wind load calculation — NBR 6123:2023' },
|
|
app_loading: { 'pt-BR': 'Carregando...', 'en-US': 'Loading...' },
|
|
|
|
// === Navegação ===
|
|
nav_home: { 'pt-BR': 'Início', 'en-US': 'Home' },
|
|
nav_warehouse: { 'pt-BR': 'Galpão', 'en-US': 'Warehouse' },
|
|
nav_cylinder: { 'pt-BR': 'Cilindro', 'en-US': 'Cylinder' },
|
|
nav_vault: { 'pt-BR': 'Abóbada', 'en-US': 'Vault' },
|
|
nav_dome: { 'pt-BR': 'Cúpula', 'en-US': 'Dome' },
|
|
nav_sign: { 'pt-BR': 'Muros/Placas', 'en-US': 'Signs/Walls' },
|
|
nav_isolated_roof: { 'pt-BR': 'Coberturas Isoladas', 'en-US': 'Isolated Roofs' },
|
|
nav_bar: { 'pt-BR': 'Barras', 'en-US': 'Bars' },
|
|
nav_bridge: { 'pt-BR': 'Pontes', 'en-US': 'Bridges' },
|
|
nav_tower: { 'pt-BR': 'Torres', 'en-US': 'Towers' },
|
|
nav_dynamics: { 'pt-BR': 'Dinâmica + Vórtices', 'en-US': 'Dynamics + Vortex' },
|
|
nav_settings: { 'pt-BR': 'Preferências', 'en-US': 'Preferences' },
|
|
nav_collapse: { 'pt-BR': 'Recolher sidebar', 'en-US': 'Collapse sidebar' },
|
|
nav_expand: { 'pt-BR': 'Expandir sidebar', 'en-US': 'Expand sidebar' },
|
|
|
|
// === Comum (botões / ações) ===
|
|
common_save: { 'pt-BR': 'Salvar', 'en-US': 'Save' },
|
|
common_cancel: { 'pt-BR': 'Cancelar', 'en-US': 'Cancel' },
|
|
common_delete: { 'pt-BR': 'Excluir', 'en-US': 'Delete' },
|
|
common_edit: { 'pt-BR': 'Editar', 'en-US': 'Edit' },
|
|
common_download: { 'pt-BR': 'Baixar', 'en-US': 'Download' },
|
|
common_clear: { 'pt-BR': 'Limpar', 'en-US': 'Clear' },
|
|
common_export: { 'pt-BR': 'Exportar', 'en-US': 'Export' },
|
|
common_import: { 'pt-BR': 'Importar', 'en-US': 'Import' },
|
|
common_apply: { 'pt-BR': 'Aplicar', 'en-US': 'Apply' },
|
|
common_close: { 'pt-BR': 'Fechar', 'en-US': 'Close' },
|
|
common_yes: { 'pt-BR': 'Sim', 'en-US': 'Yes' },
|
|
common_no: { 'pt-BR': 'Não', 'en-US': 'No' },
|
|
common_ok: { 'pt-BR': 'OK', 'en-US': 'OK' },
|
|
common_loading: { 'pt-BR': 'Carregando...', 'en-US': 'Loading...' },
|
|
common_error: { 'pt-BR': 'Erro', 'en-US': 'Error' },
|
|
common_warning: { 'pt-BR': 'Atenção', 'en-US': 'Warning' },
|
|
common_success: { 'pt-BR': 'Sucesso', 'en-US': 'Success' },
|
|
common_back: { 'pt-BR': 'Voltar', 'en-US': 'Back' },
|
|
common_next: { 'pt-BR': 'Próximo', 'en-US': 'Next' },
|
|
|
|
// === Exportação ===
|
|
export_csv: { 'pt-BR': 'Exportar CSV', 'en-US': 'Export CSV' },
|
|
export_pdf: { 'pt-BR': 'Exportar PDF', 'en-US': 'Export PDF' },
|
|
export_ftool: { 'pt-BR': 'Ftool', 'en-US': 'Ftool' },
|
|
export_snapshot: { 'pt-BR': 'Exportar estado', 'en-US': 'Export state' },
|
|
export_import: { 'pt-BR': 'Importar projeto', 'en-US': 'Import project' },
|
|
|
|
// === Configurações / Tema ===
|
|
settings_appearance: { 'pt-BR': 'Aparência', 'en-US': 'Appearance' },
|
|
settings_appearance_desc: { 'pt-BR': 'Tema do aplicativo (claro/escuro/sistema).', 'en-US': 'Application theme (light/dark/system).' },
|
|
settings_theme_light: { 'pt-BR': 'Claro', 'en-US': 'Light' },
|
|
settings_theme_dark: { 'pt-BR': 'Escuro', 'en-US': 'Dark' },
|
|
settings_theme_system: { 'pt-BR': 'Sistema', 'en-US': 'System' },
|
|
settings_effective: { 'pt-BR': 'Tema efetivo atual', 'en-US': 'Current effective theme' },
|
|
|
|
settings_projects: { 'pt-BR': 'Projetos Salvos', 'en-US': 'Saved Projects' },
|
|
settings_projects_desc: { 'pt-BR': 'Persistência local via IndexedDB.', 'en-US': 'Local persistence via IndexedDB.' },
|
|
settings_projects_count: { 'pt-BR': '{count} projeto(s) armazenado(s).', 'en-US': '{count} project(s) stored.' },
|
|
settings_no_projects: { 'pt-BR': 'Nenhum projeto salvo ainda.', 'en-US': 'No saved projects yet.' },
|
|
settings_importing: { 'pt-BR': 'Importando...', 'en-US': 'Importing...' },
|
|
settings_import_success: { 'pt-BR': 'Importação concluída', 'en-US': 'Import successful' },
|
|
settings_import_error: { 'pt-BR': 'Falha na importação', 'en-US': 'Import failed' },
|
|
settings_import_module: { 'pt-BR': 'Módulo', 'en-US': 'Module' },
|
|
settings_import_project: { 'pt-BR': 'Projeto', 'en-US': 'Project' },
|
|
settings_import_fields: { 'pt-BR': 'Campos aplicados ({count})', 'en-US': 'Applied fields ({count})' },
|
|
settings_import_warnings: { 'pt-BR': 'Avisos', 'en-US': 'Warnings' },
|
|
|
|
settings_state: { 'pt-BR': 'Estado Atual', 'en-US': 'Current State' },
|
|
settings_state_desc: { 'pt-BR': 'Snapshot do windStore para debug.', 'en-US': 'windStore snapshot for debug.' },
|
|
|
|
settings_about: { 'pt-BR': 'Sobre', 'en-US': 'About' },
|
|
settings_about_desc: { 'pt-BR': 'Cálculo de cargas de vento conforme NBR 6123:2023.', 'en-US': 'Wind load calculation per NBR 6123:2023.' },
|
|
settings_stack: { 'pt-BR': 'Stack', 'en-US': 'Stack' },
|
|
|
|
// === Galpão / Warehouse ===
|
|
geom_width: { 'pt-BR': 'Largura', 'en-US': 'Width' },
|
|
geom_length: { 'pt-BR': 'Comprimento', 'en-US': 'Length' },
|
|
geom_height: { 'pt-BR': 'Altura', 'en-US': 'Height' },
|
|
geom_pitch: { 'pt-BR': 'Inclinação', 'en-US': 'Roof pitch' },
|
|
geom_clearance: { 'pt-BR': 'Distância do solo', 'en-US': 'Ground clearance' },
|
|
geom_diameter: { 'pt-BR': 'Diâmetro', 'en-US': 'Diameter' },
|
|
|
|
tab_geometry: { 'pt-BR': 'Geometria', 'en-US': 'Geometry' },
|
|
tab_norm: { 'pt-BR': 'NBR', 'en-US': 'NBR' },
|
|
tab_cpi: { 'pt-BR': 'Cpi', 'en-US': 'Cpi' },
|
|
tab_local: { 'pt-BR': 'Local', 'en-US': 'Location' },
|
|
tab_result: { 'pt-BR': 'Resultados', 'en-US': 'Results' },
|
|
|
|
wind_direction: { 'pt-BR': 'Direção do Vento', 'en-US': 'Wind Direction' },
|
|
wind_perpendicular: { 'pt-BR': '0° (Perpendicular à largura)', 'en-US': '0° (Perpendicular to width)' },
|
|
wind_parallel: { 'pt-BR': '90° (Paralelo à largura)', 'en-US': '90° (Parallel to width)' },
|
|
|
|
// === Cargas Lineares (M9.2) ===
|
|
linear_loads_title: { 'pt-BR': 'Cargas Lineares (M9.2)', 'en-US': 'Linear Loads (M9.2)' },
|
|
linear_loads_desc: { 'pt-BR': 'kN/m por barra para software estrutural (Ftool, SAP2000, Eberick, TQS).', 'en-US': 'kN/m per member for structural software (Ftool, SAP2000, etc).' },
|
|
linear_loads_frame_spacing: { 'pt-BR': 'Espaçamento entre pórticos (m)', 'en-US': 'Frame spacing (m)' },
|
|
linear_loads_purlin_spacing: { 'pt-BR': 'Espaçamento entre terças (m)', 'en-US': 'Purlin spacing (m)' },
|
|
linear_loads_frame_help: { 'pt-BR': 'Vão entre pórticos principais (eixo X)', 'en-US': 'Span between main frames (X axis)' },
|
|
linear_loads_purlin_help: { 'pt-BR': 'Distância entre terças no plano do telhado', 'en-US': 'Distance between purlins in roof plane' },
|
|
linear_loads_tab_pillars: { 'pt-BR': 'Pilares', 'en-US': 'Columns' },
|
|
linear_loads_tab_purlins: { 'pt-BR': 'Terças', 'en-US': 'Purlins' },
|
|
linear_loads_tab_reactions: { 'pt-BR': 'Reações', 'en-US': 'Reactions' },
|
|
linear_loads_pillar_windward: { 'pt-BR': 'Barlavento', 'en-US': 'Windward' },
|
|
linear_loads_pillar_leeward: { 'pt-BR': 'Sotavento', 'en-US': 'Leeward' },
|
|
linear_loads_pillar_side1: { 'pt-BR': 'Lateral 1', 'en-US': 'Side 1' },
|
|
linear_loads_pillar_side2: { 'pt-BR': 'Lateral 2', 'en-US': 'Side 2' },
|
|
linear_loads_sign_positive: { 'pt-BR': 'Sinal positivo = empuxo (empurrando o pilar para dentro). Sinal negativo = sucção (puxando para fora).', 'en-US': 'Positive sign = pressure (pushing the column inward). Negative sign = suction (pulling outward).' },
|
|
linear_loads_purlin_apply: { 'pt-BR': 'Cargas já com fator cos θ aplicado (terça é horizontal). Aplicar a barra como uniformemente distribuída no Ftool/SAP2000.', 'en-US': 'Loads already include cos θ factor (purlin is horizontal). Apply as uniformly distributed in Ftool/SAP2000.' },
|
|
linear_loads_reaction_base: { 'pt-BR': 'Reações na base dos pilares (kN) e momentos (kN·m)', 'en-US': 'Pillar base reactions (kN) and moments (kN·m)' },
|
|
linear_loads_total_reaction: { 'pt-BR': 'Reação total', 'en-US': 'Total reaction' },
|
|
linear_loads_warning_simplified: { 'pt-BR': 'Reações são estimativas simplificadas (pilar em balanço). Para pórticos com continuidade nos nós, usar software estrutural com análise elástica.', 'en-US': 'Reactions are simplified estimates (cantilever column). For frames with continuity at nodes, use structural software with elastic analysis.' },
|
|
|
|
// === Captura 3D (M9.3) ===
|
|
scene_capture_title: { 'pt-BR': 'Captura 3D (M9.3)', 'en-US': '3D Capture (M9.3)' },
|
|
scene_capture_desc: { 'pt-BR': 'Screenshot da cena 3D para incluir no PDF ou exportar isoladamente.', 'en-US': 'Screenshot of 3D scene for PDF or standalone export.' },
|
|
scene_capture_format: { 'pt-BR': 'Formato de Saída', 'en-US': 'Output Format' },
|
|
scene_capture_width: { 'pt-BR': 'Largura máxima (px)', 'en-US': 'Max width (px)' },
|
|
scene_capture_quality: { 'pt-BR': 'Qualidade JPEG', 'en-US': 'JPEG Quality' },
|
|
scene_capture_btn: { 'pt-BR': 'Capturar cena atual', 'en-US': 'Capture current scene' },
|
|
scene_capture_waiting: { 'pt-BR': 'Aguardando canvas...', 'en-US': 'Waiting for canvas...' },
|
|
scene_capture_capturing: { 'pt-BR': 'Capturando...', 'en-US': 'Capturing...' },
|
|
scene_capture_preview: { 'pt-BR': 'Preview', 'en-US': 'Preview' },
|
|
scene_capture_pdf_hint: { 'pt-BR': 'A imagem será incluída automaticamente no PDF quando você exportar após capturar.', 'en-US': 'The image is automatically included in the PDF when you export after capturing.' },
|
|
scene_capture_width_help: { 'pt-BR': '0 mantém resolução original do canvas. 1600 px é ideal para PDF A4.', 'en-US': '0 keeps the original canvas resolution. 1600 px is ideal for A4 PDF.' },
|
|
|
|
// === Ftool (M9.4) ===
|
|
ftool_title: { 'pt-BR': 'Exportar para Ftool (M9.4)', 'en-US': 'Export to Ftool (M9.4)' },
|
|
ftool_desc: { 'pt-BR': 'Pórtico 2D com nós, barras e cargas lineares para Ftool (PUC-Rio).', 'en-US': '2D frame with nodes, members and linear loads for Ftool (PUC-Rio).' },
|
|
ftool_content: { 'pt-BR': 'Conteúdo do arquivo .txt', 'en-US': 'Content of the .txt file' },
|
|
ftool_import_hint: { 'pt-BR': 'Import no Ftool: File → Import', 'en-US': 'Import in Ftool: File → Import' },
|
|
ftool_sign_convention: { 'pt-BR': 'Sinal de carga: positivo = na direção positiva do eixo Y (empuxo). Cargas de coluna em GlobalX (horizontal).', 'en-US': 'Load sign: positive = in the positive Y-axis direction (pressure). Column loads on GlobalX (horizontal).' },
|
|
ftool_download: { 'pt-BR': 'Baixar galpao_ftool.txt', 'en-US': 'Download galpao_ftool.txt' },
|
|
|
|
// === Auditoria LLM ===
|
|
audit_title: { 'pt-BR': 'Auditoria LLM', 'en-US': 'LLM Audit' },
|
|
audit_desc: { 'pt-BR': 'Validação dos cálculos de vento usando LLMs sequenciais. 100+ cenários cobrindo todos os módulos.', 'en-US': 'Wind calculation validation using sequential LLMs. 100+ scenarios covering all modules.' },
|
|
audit_run: { 'pt-BR': 'Executar Auditoria', 'en-US': 'Run Audit' },
|
|
audit_cancel: { 'pt-BR': 'Cancelar', 'en-US': 'Cancel' },
|
|
audit_export_pdf: { 'pt-BR': 'Exportar PDF', 'en-US': 'Export PDF' },
|
|
audit_provider: { 'pt-BR': 'Provedor', 'en-US': 'Provider' },
|
|
audit_model: { 'pt-BR': 'Modelo', 'en-US': 'Model' },
|
|
audit_apikey: { 'pt-BR': 'API Key', 'en-US': 'API Key' },
|
|
audit_baseurl: { 'pt-BR': 'Base URL (opcional)', 'en-US': 'Base URL (optional)' },
|
|
audit_generating: { 'pt-BR': 'Gerando cenários...', 'en-US': 'Generating scenarios...' },
|
|
audit_running: { 'pt-BR': 'Processando cenários...', 'en-US': 'Running scenarios...' },
|
|
audit_progress: { 'pt-BR': '{current} de {total}', 'en-US': '{current} of {total}' },
|
|
audit_no_apikey: { 'pt-BR': 'Informe uma API key para continuar.', 'en-US': 'Provide an API key to continue.' },
|
|
audit_results: { 'pt-BR': 'Resultados', 'en-US': 'Results' },
|
|
audit_summary: { 'pt-BR': '{passed}/{total} PASS — W:{warnings} F:{failed}', 'en-US': '{passed}/{total} PASS — W:{warnings} F:{failed}' },
|
|
audit_total: { 'pt-BR': 'Total', 'en-US': 'Total' },
|
|
audit_module: { 'pt-BR': 'Módulo', 'en-US': 'Module' },
|
|
audit_click_module: { 'pt-BR': 'Clique em um módulo para ver detalhes.', 'en-US': 'Click a module to view details.' },
|
|
audit_checks: { 'pt-BR': 'Verificações', 'en-US': 'Checks' },
|
|
audit_expected: { 'pt-BR': 'Esperado', 'en-US': 'Expected' },
|
|
audit_actual: { 'pt-BR': 'Obtido', 'en-US': 'Actual' },
|
|
audit_inputs: { 'pt-BR': 'Entradas', 'en-US': 'Inputs' },
|
|
audit_detail_title: { 'pt-BR': 'Detalhes do Cenário', 'en-US': 'Scenario Details' },
|
|
audit_copy_module: { 'pt-BR': 'Copiar para o módulo', 'en-US': 'Copy to module' },
|
|
audit_close: { 'pt-BR': 'Fechar', 'en-US': 'Close' },
|
|
audit_export_popup: { 'pt-BR': 'Exportar PDF deste cenário', 'en-US': 'Export PDF of this scenario' },
|
|
|
|
// === Home (App.tsx) ===
|
|
home_full_coverage: { 'pt-BR': 'Cobertura completa da norma', 'en-US': 'Full standard coverage' },
|
|
|
|
// === Idioma ===
|
|
language: { 'pt-BR': 'Idioma', 'en-US': 'Language' },
|
|
language_pt: { 'pt-BR': 'Português (BR)', 'en-US': 'Portuguese (BR)' },
|
|
language_en: { 'pt-BR': 'Inglês (EUA)', 'en-US': 'English (US)' },
|
|
|
|
// === Erros ===
|
|
error_generic: { 'pt-BR': 'Erro desconhecido', 'en-US': 'Unknown error' },
|
|
error_invalid_json: { 'pt-BR': 'JSON inválido', 'en-US': 'Invalid JSON' },
|
|
error_unknown_format: { 'pt-BR': 'Formato não reconhecido', 'en-US': 'Unknown format' },
|
|
};
|
|
|
|
/** Substitui {placeholder} por valores fornecidos. */
|
|
function interpolate(template: string, params?: Record<string, string | number>): string {
|
|
if (!params) return template;
|
|
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
const v = params[key];
|
|
return v === undefined ? `{${key}}` : String(v);
|
|
});
|
|
}
|
|
|
|
/** Tradução pura (sem hook). */
|
|
export function t(
|
|
key: string,
|
|
locale: Locale = DEFAULT_LOCALE,
|
|
params?: Record<string, string | number>,
|
|
): string {
|
|
const entry = translations[key];
|
|
if (entry) return interpolate(entry[locale] ?? entry[DEFAULT_LOCALE] ?? key, params);
|
|
// Fallback: retorna a chave
|
|
return params ? interpolate(key, params) : key;
|
|
}
|
|
|
|
/** Lista todas as chaves disponíveis (útil para debug). */
|
|
export function listKeys(): string[] {
|
|
return Object.keys(translations).sort();
|
|
}
|
|
|
|
/** Detecta locale preferido do navegador. */
|
|
export function detectBrowserLocale(): Locale {
|
|
if (typeof navigator === 'undefined') return DEFAULT_LOCALE;
|
|
const lang = navigator.language;
|
|
if (lang.startsWith('pt')) return 'pt-BR';
|
|
if (lang.startsWith('en')) return 'en-US';
|
|
return DEFAULT_LOCALE;
|
|
} |