feat(V2-1d): packages/i18n 추출 — Electron 독립 i18n 엔진

packages/i18n (@d3ro/i18n) 신규:
- src/locales/ 12개 JSON (ko/en/ja/zh/zh-TW/es/fr/de/pt/ru/vi/th)
- src/index.tsx: TFunction/Locale/LOCALE_META/resolveTranslation/
  포맷 유틸(createFormatDate/Number/RelativeDate/Time)/
  I18nContext/useI18n/I18nProvider

Electron 독립성 확보 (monorepo 원칙 준수):
- I18nStorage interface 신설 (load/save 어댑터 추상화)
- window.electronAPI.config 직접 호출 제거
- I18nProvider는 storage prop으로 영속화 어댑터 주입
- 기본값 noopStorage (메모리 한정, 세션 범위)
- 결과: packages/i18n은 React에만 의존, web/mobile에서 재사용 가능

apps/desktop 어댑터:
- App.tsx에 electronI18nStorage 구현 (config.get/set 래핑)
- <I18nProvider storage={electronI18nStorage}>로 주입

apps/desktop 설정:
- package.json: @d3ro/i18n '*' dep 추가
- tsconfig.node/web.json paths에 @d3ro/i18n 추가
- electron.vite.config.ts alias + externalize exclude 추가
- vitest.config.ts alias 추가

일괄 치환 (29 파일):
- ./i18n, ../i18n, ../../i18n → @d3ro/i18n

apps/desktop/src/renderer/i18n/ 디렉토리 완전 제거.

검증: typecheck + build + dev 런타임 모두 통과.
Phase V2-1 전체 완료 (a/b/c/d).
This commit is contained in:
yunchan8804 2026-04-08 14:57:27 +09:00
parent a041f1b6a9
commit 3524e958ba
51 changed files with 175 additions and 66 deletions

View file

@ -0,0 +1,282 @@
{
"app.name": "D3RO Voice",
"app.tagline": "Asistente de voz con IA local",
"nav.dashboard": "Panel",
"nav.history": "Historial",
"nav.dictionary": "Diccionario",
"nav.commands": "Comandos",
"nav.settings": "Ajustes",
"dashboard.sessionOverview": "Resumen de sesión",
"dashboard.systemStatus": "Estado del sistema",
"dashboard.sessionsToday": "Sesiones hoy",
"dashboard.pressToRecord": "Pulsa {{key}} para empezar a grabar",
"dashboard.hotkeyNotSet": "Atajo no configurado",
"dashboard.words": "palabras",
"dashboard.total": "total",
"dashboard.streak": "racha",
"dashboard.days": "días",
"dashboard.recording": "grabando",
"dashboard.sessions": "sesiones",
"dashboard.today": "hoy",
"dashboard.recentTranscriptions": "Transcripciones recientes",
"dashboard.noHistory": "Sin historial — pulsa el atajo para empezar a grabar",
"dashboard.copy": "Copiar",
"dashboard.entries": "{{count}} entradas",
"dashboard.stat": "estadísticas",
"dashboard.sys": "sistema",
"history.title": "Historial de transcripciones",
"history.search": "Buscar...",
"history.entries": "{{count}} entradas",
"history.loading": "Cargando...",
"history.noResults": "Sin resultados",
"history.noHistory": "Sin historial — empieza a grabar",
"history.count": "{{count}} entradas",
"dictionary.title": "Diccionario personalizado",
"dictionary.words": "{{count}} palabras",
"dictionary.add": "Añadir",
"dictionary.search": "Buscar...",
"dictionary.loading": "Cargando...",
"dictionary.noResults": "Sin resultados",
"dictionary.noWords": "Sin palabras — añade palabras personalizadas para mejorar la precisión del STT",
"dictionary.used": "usado {{count}} veces",
"dictionary.editTitle": "Editar palabra",
"dictionary.addTitle": "Añadir palabra",
"dictionary.word": "Palabra",
"dictionary.pronunciation": "Pronunciación (opcional)",
"commands.title": "Comandos LLM",
"commands.count": "{{count}} comandos",
"commands.add": "Añadir",
"commands.activeCommand": "Comando activo",
"commands.none": "Ninguno",
"commands.loading": "Cargando...",
"commands.noCommands": "Sin comandos — haz clic en añadir para crear uno",
"commands.editTitle": "Editar comando",
"commands.addTitle": "Añadir comando",
"commands.name": "Nombre",
"commands.description": "Descripción",
"commands.promptTemplate": "Plantilla de prompt",
"commands.promptHelp": "{{text}} se reemplazará por el texto transcrito",
"commands.defaultPrompt": "Por favor, mejora {{text}}.",
"settings.title": "Ajustes",
"settings.tabs.general": "General",
"settings.tabs.audio": "Audio",
"settings.tabs.stt": "STT",
"settings.tabs.llm": "LLM",
"settings.tabs.about": "Acerca de",
"settings.shortcuts": "Atajos",
"settings.dictation": "Dictado",
"settings.dictation.desc": "Mantén pulsado para hablar. Al soltar, comienza la transcripción.",
"settings.agent": "Modo agente",
"settings.agent.descWithKey": "Haz doble clic en {{key}} para entrar en el modo agente.",
"settings.agent.descNoKey": "Primero configura el atajo de dictado.",
"settings.oneTouch": "Modo un toque",
"settings.oneTouch.desc": "Pulsa para iniciar, pulsa de nuevo para detener. Requiere un atajo independiente.",
"settings.key": "Tecla",
"settings.notSet": "No configurado",
"settings.enabled": "Activado",
"settings.disabled": "Desactivado",
"settings.interface": "Interfaz",
"settings.theme": "Tema",
"settings.theme.system": "Sistema",
"settings.theme.light": "Claro",
"settings.theme.dark": "Oscuro",
"settings.language": "Idioma",
"settings.appBehavior": "Comportamiento de la app",
"settings.closeToTray": "Minimizar a la bandeja",
"settings.autoLaunch": "Iniciar con el sistema",
"settings.autoInsert": "Insertar texto automáticamente tras transcripción",
"settings.soundEffects": "Efectos de sonido",
"settings.microphone": "Micrófono",
"settings.inputDevice": "Dispositivo de entrada",
"settings.deviceDefault": "(predeterminado)",
"settings.textInsert": "Inserción de texto",
"settings.insertMethod": "Método de inserción",
"settings.insertClipboard": "Portapapeles (Ctrl+V)",
"settings.insertKeyboard": "Escritura con teclado",
"settings.whisperModel": "Modelo Whisper",
"settings.model.tiny": "tiny (39 MB, más rápido)",
"settings.model.base": "base (74 MB, equilibrado)",
"settings.model.small": "small (244 MB, bueno)",
"settings.model.medium": "medium (769 MB, muy bueno)",
"settings.model.large": "large-v3 (1.5 GB, mejor)",
"settings.sttLanguage": "Idioma de reconocimiento",
"settings.sttLang.auto": "Detección automática",
"settings.ollamaServer": "Servidor Ollama",
"settings.ollamaUrl": "URL del servidor Ollama",
"settings.ollamaHint": "D3RO Voice se conecta automáticamente cuando Ollama está en ejecución. Descarga modelos directamente desde Ollama (ej: ollama pull qwen3:4b).",
"settings.postProcess": "Postprocesado de voz",
"settings.defaultAction": "Acción de postprocesado predeterminada",
"settings.action.none": "Ninguna (texto original sin cambios)",
"settings.action.refine": "Refinar (gramática + naturalidad)",
"settings.action.translate": "Traducir",
"settings.action.summarize": "Resumir",
"settings.action.grammar": "Corrección gramatical",
"settings.action.custom": "Prompt personalizado",
"settings.actionHint": "El postprocesado LLM seleccionado se aplica al texto transcrito tras grabar con el atajo. Solo funciona cuando Ollama está conectado.",
"settings.about.version": "Versión",
"settings.about.techStack": "Stack tecnológico",
"settings.about.techStackValue": "Electron + React 19 + MUI 7 + TypeScript",
"settings.about.voiceEngine": "Motor de voz",
"settings.about.voiceEngineValue": "STT: faster-whisper (local) / LLM: Ollama (local)",
"settings.about.description": "Asistente de voz con IA completamente local, basado en ingeniería inversa de Speakly. Funciona sin dependencias en la nube.",
"settings.about.restartOnboarding": "Ver guía de configuración inicial de nuevo",
"status.ollama": "OLLAMA",
"status.offline": "OFFLINE",
"status.nudge.title": "Ollama no está en ejecución",
"status.nudge.desc": "Se necesita Ollama para el postprocesado LLM (traducción, resumen, etc.).",
"status.nudge.guide": "Ver guía de instalación →",
"onboarding.welcome.title": "D3RO-VOICE",
"onboarding.welcome.desc": "Sin escribir, con voz. Tu asistente de voz con IA local.",
"onboarding.welcome.start": "Comenzar",
"onboarding.mic.title": "Configurar micrófono",
"onboarding.mic.desc": "Selecciona el micrófono que quieres usar. Puedes cambiarlo más tarde en los ajustes.",
"onboarding.hotkey.title": "Configurar atajo",
"onboarding.hotkey.desc": "Configura el atajo de dictado. Se grabará mientras mantengas la tecla pulsada.",
"onboarding.hotkey.notSet": "No se ha configurado ningún atajo",
"onboarding.hotkey.change": "Cambiar atajo",
"onboarding.hotkey.set": "Configurar atajo",
"onboarding.ollama.title": "Instalar Ollama (opcional)",
"onboarding.ollama.desc": "Se necesita Ollama para el postprocesado LLM como traducción o resumen. El dictado de voz funciona sin Ollama.",
"onboarding.ollama.download": "Descargar Ollama",
"onboarding.ollama.modelHint": "Tras instalar, descarga un modelo desde la terminal",
"onboarding.done.title": "¡Configuración completa!",
"onboarding.done.descWithKey": "Mantén pulsado {{key}} y habla para convertir tu voz en texto.",
"onboarding.done.descNoKey": "Configura un atajo en los ajustes para empezar a dictar.",
"onboarding.done.start": "Comenzar",
"onboarding.back": "Atrás",
"onboarding.next": "Siguiente",
"hotkey.title": "Configurar atajo",
"hotkey.dictationTitle": "Configurar atajo de dictado",
"hotkey.oneTouchTitle": "Configurar atajo de modo un toque",
"hotkey.prompt": "Pulsa una combinación de teclas...",
"hotkey.ready": "✓ {{keys}} — pulsa guardar",
"hotkey.noKey": "Por favor, introduce una tecla",
"hotkey.reserved": "{{keys}} es un atajo reservado por el sistema",
"hotkey.current": "Actual: {{keys}}",
"hotkey.hint": "Introduce una combinación (ej: Ctrl+Shift+Q) o una tecla sola (ej: F5)",
"hotkey.reset": "Volver a introducir",
"ollama.title": "GUÍA DE CONFIGURACIÓN DE OLLAMA",
"ollama.step1.title": "PASO 1 — Instalar Ollama",
"ollama.step1.desc": "Ollama es una herramienta gratuita para ejecutar LLMs de forma local.",
"ollama.step2.title": "PASO 2 — Descargar un modelo",
"ollama.step2.desc": "Ejecuta el siguiente comando en la terminal para descargar el modelo recomendado:",
"ollama.step2.alt": "O un modelo más grande: ollama pull qwen3:8b (más preciso, más lento)",
"ollama.step3.title": "PASO 3 — Conexión automática",
"ollama.step3.desc": "D3RO-VOICE detecta automáticamente cuando Ollama está en ejecución. Cuando el LED de la barra de estado pase de rojo a verde, ¡estará listo!",
"service.sttEngine": "MOTOR STT",
"service.ollamaLlm": "OLLAMA LLM",
"service.hotkeyHook": "ATAJO DE TECLADO",
"service.audioInput": "ENTRADA DE AUDIO",
"service.ready": "LISTO",
"service.connected": "CONECTADO",
"service.offline": "OFFLINE",
"service.active": "ACTIVO",
"service.standby": "EN ESPERA",
"common.cancel": "Cancelar",
"common.save": "Guardar",
"common.delete": "Eliminar",
"common.add": "Añadir",
"common.edit": "Editar",
"common.close": "Cerrar",
"common.confirm": "Confirmar",
"common.loading": "Cargando...",
"common.copy": "Copiar",
"common.test": "Probar",
"common.stop": "Detener",
"date.today": "Hoy",
"date.yesterday": "Ayer",
"settings.theme.nord": "Nord",
"settings.theme.solarized": "Solarized",
"settings.theme.catppuccin": "Catppuccin",
"settings.theme.dracula": "Dracula",
"settings.sttLang.ko": "Coreano",
"settings.sttLang.en": "Inglés",
"settings.sttLang.ja": "Japonés",
"settings.sttLang.zh": "Chino",
"template.outputHelperText": "Use {{nombreCampo}} para marcadores de posición",
"nav.meeting": "Reunión",
"meeting.title": "Modo Reunión",
"meeting.newMeeting": "Nueva Reunión",
"meeting.startRecording": "Iniciar Grabación",
"meeting.stopRecording": "Detener Grabación",
"meeting.recording": "Grabando",
"meeting.processing": "Generando actas...",
"meeting.completed": "Completado",
"meeting.error": "Error",
"meeting.elapsed": "Tiempo transcurrido",
"meeting.memoPlaceholder": "Escribe un memo (Enter para añadir)",
"meeting.memoAdded": "Memo añadido",
"meeting.noSessions": "Sin grabaciones de reuniones",
"meeting.noSessionsDesc": "Pulsa Nueva Reunión para empezar a grabar",
"meeting.summary": "Resumen",
"meeting.decisions": "Decisiones",
"meeting.actionItems": "Tareas",
"meeting.timeline": "Línea de tiempo",
"meeting.transcript": "Transcripción bruta",
"meeting.memos": "Memos",
"meeting.exportPdf": "Exportar PDF",
"meeting.exportMarkdown": "Exportar Markdown",
"meeting.delete": "Eliminar",
"meeting.deleteConfirm": "¿Eliminar este registro de reunión?",
"meeting.backToList": "Volver a la lista",
"meeting.notificationTitle": "Actas listas",
"meeting.notificationBody": "Las actas de la reunión han sido generadas.",
"meeting.sessions": "{{count}} sesiones",
"meeting.duration": "{{minutes}} min",
"meeting.editTitle": "Editar título",
"meeting.untitled": "Reunión sin título",
"meeting.processingStep.merging": "Fusionando transcripción...",
"meeting.processingStep.generating": "Generando actas con LLM...",
"meeting.processingStep.parsing": "Analizando actas...",
"meeting.processingStep.saving": "Guardando...",
"meeting.processingStep.notifying": "Enviando notificación...",
"meeting.transcriptTab": "Transcripción",
"meeting.addDocument": "Agregar documento",
"meeting.selectTemplate": "Seleccionar plantilla",
"meeting.generate": "Generar",
"meeting.generating": "Generando documento...",
"meeting.editMode": "Editar",
"meeting.previewMode": "Vista previa",
"meeting.modified": "Modificado",
"meeting.originalText": "Original",
"meeting.editedText": "Editado",
"meeting.exportDocx": "Exportar DOCX",
"meeting.exportTxt": "Exportar TXT",
"meeting.exportFormat": "Exportar",
"meeting.customPrompt": "Prompt personalizado",
"meeting.templateName": "Nombre de plantilla",
"meeting.templatePrompt": "Prompt de IA",
"meeting.templateSave": "Guardar plantilla",
"meeting.documentDeleted": "Documento eliminado",
"meeting.autoSaved": "Guardado automáticamente",
"meeting.minutesTemplate": "Actas de reunión",
"meeting.reportTemplate": "Informe",
"meeting.ideaNoteTemplate": "Nota de ideas",
"meeting.customTemplate": "Personalizado",
"meeting.downloadTranscript": "Descargar transcripción",
"meeting.polish": "Pulir con IA",
"meeting.polishing": "IA puliendo transcripción...",
"meeting.polished": "Pulido con IA completado",
"meeting.mindmapTemplate": "Mapa mental",
"meeting.chat": "Chat IA",
"meeting.chatPlaceholder": "Pregunta sobre la reunión...",
"meeting.chatSend": "Enviar",
"meeting.chatClear": "Limpiar chat",
"meeting.copyToClipboard": "Copiar al portapapeles",
"meeting.copied": "Copiado",
"meeting.chatCollapse": "Contraer chat",
"meeting.chatExpand": "Expandir chat",
"meeting.exportMd": "Markdown (.md)",
"meeting.exportPdfFmt": "PDF (.pdf)",
"meeting.polishFailed": "Error al pulir con IA",
"meeting.viewDetailError": "Error al cargar detalles de la reunión",
"meeting.startRecordingError": "Error al iniciar la grabación",
"meeting.diarize": "Identificar hablantes",
"meeting.diarizing": "Identificando hablantes...",
"meeting.diarizeComplete": "Identificación completada",
"meeting.speaker": "Hablante",
"settings.hfToken": "Token de HuggingFace",
"settings.hfTokenHint": "Se necesita un token de HuggingFace para identificar hablantes",
"settings.diarization": "Identificación de hablantes",
"settings.diarizationHint": "Identifica automáticamente los hablantes al finalizar la grabación"
}