From de0d3977b701a5bd4092d945d0f5cf342a11fb25 Mon Sep 17 00:00:00 2001 From: Luis Novo Date: Thu, 15 Jan 2026 18:55:55 -0300 Subject: [PATCH] feat(i18n): add Brazilian Portuguese (pt-BR) translation (#434) - Add complete pt-BR locale with 750+ translated strings - Register pt-BR in locale registry and language selector - Add date-fns ptBR locale for date formatting - Update README and CLAUDE.md documentation --- README.md | 2 +- frontend/src/CLAUDE.md | 2 +- .../src/components/common/LanguageToggle.tsx | 8 +- frontend/src/lib/locales/CLAUDE.md | 1 + frontend/src/lib/locales/en-US/index.ts | 1 + frontend/src/lib/locales/index.ts | 7 +- frontend/src/lib/locales/pt-BR/index.ts | 951 ++++++++++++++++++ frontend/src/lib/locales/zh-CN/index.ts | 1 + frontend/src/lib/locales/zh-TW/index.ts | 1 + frontend/src/lib/utils/date-locale.ts | 3 +- 10 files changed, 971 insertions(+), 6 deletions(-) create mode 100644 frontend/src/lib/locales/pt-BR/index.ts diff --git a/README.md b/README.md index e4f72ec..53e78ec 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ In a world dominated by Artificial Intelligence, having the ability to think - 🎙️ **Generate professional podcasts** - Advanced multi-speaker podcast generation - 🔍 **Search intelligently** - Full-text and vector search across all your content - 💬 **Chat with context** - AI conversations powered by your research -- 🌐 **Multi-language UI** - Chinese (Simplified & Traditional) support, more languages coming soon! +- 🌐 **Multi-language UI** - English, Portuguese, and Chinese (Simplified & Traditional) support Learn more about our project at [https://www.open-notebook.ai](https://www.open-notebook.ai) diff --git a/frontend/src/CLAUDE.md b/frontend/src/CLAUDE.md index 3dd7cbc..bfc764b 100644 --- a/frontend/src/CLAUDE.md +++ b/frontend/src/CLAUDE.md @@ -62,7 +62,7 @@ User interactions trigger mutations/queries via hooks, which communicate with th - Ensures type safety across API calls and store mutations #### `lib/locales/` — Internationalization (i18n) -- **Locale files** (`en-US/`, `zh-CN/`, `zh-TW/`): Translation strings organized by feature +- **Locale files** (`en-US/`, `pt-BR/`, `zh-CN/`, `zh-TW/`): Translation strings organized by feature - **`i18n.ts`**: i18next configuration with language detection - **`use-translation.ts`**: Custom hook with Proxy-based `t.section.key` access pattern - **Pattern**: Components call `useTranslation()` hook; access strings via `t.common.save`, `t.notebooks.title` diff --git a/frontend/src/components/common/LanguageToggle.tsx b/frontend/src/components/common/LanguageToggle.tsx index aa029cf..274c5d8 100644 --- a/frontend/src/components/common/LanguageToggle.tsx +++ b/frontend/src/components/common/LanguageToggle.tsx @@ -46,12 +46,18 @@ export function LanguageToggle({ iconOnly = false }: LanguageToggleProps) { > {t.common.chinese} - setLanguage('zh-TW')} className={currentLang === 'zh-TW' || currentLang.startsWith('zh-Hant') ? 'bg-accent' : ''} > {t.common.traditionalChinese} + setLanguage('pt-BR')} + className={currentLang === 'pt-BR' || currentLang.startsWith('pt') ? 'bg-accent' : ''} + > + {t.common.portuguese} + ) diff --git a/frontend/src/lib/locales/CLAUDE.md b/frontend/src/lib/locales/CLAUDE.md index 7e430bd..b9df0a1 100644 --- a/frontend/src/lib/locales/CLAUDE.md +++ b/frontend/src/lib/locales/CLAUDE.md @@ -15,6 +15,7 @@ lib/ └── locales/ ├── index.ts # Locale registry and type exports ├── en-US/index.ts # English translations + ├── pt-BR/index.ts # Brazilian Portuguese translations ├── zh-CN/index.ts # Simplified Chinese translations └── zh-TW/index.ts # Traditional Chinese translations ``` diff --git a/frontend/src/lib/locales/en-US/index.ts b/frontend/src/lib/locales/en-US/index.ts index 9cdee60..9494a7d 100644 --- a/frontend/src/lib/locales/en-US/index.ts +++ b/frontend/src/lib/locales/en-US/index.ts @@ -128,6 +128,7 @@ export const enUS = { comingSoon: "Coming soon", retry: "Try Again", traditionalChinese: "繁體中文", + portuguese: "Português", completed: "completed", saveSuccess: "Saved successfully", contextModes: { diff --git a/frontend/src/lib/locales/index.ts b/frontend/src/lib/locales/index.ts index fda8858..f559220 100644 --- a/frontend/src/lib/locales/index.ts +++ b/frontend/src/lib/locales/index.ts @@ -1,16 +1,18 @@ import { zhCN } from './zh-CN'; import { enUS } from './en-US'; import { zhTW } from './zh-TW'; +import { ptBR } from './pt-BR'; export const resources = { 'zh-CN': { translation: zhCN }, 'en-US': { translation: enUS }, 'zh-TW': { translation: zhTW }, + 'pt-BR': { translation: ptBR }, } as const; export type TranslationKeys = typeof enUS; -export type LanguageCode = 'zh-CN' | 'en-US' | 'zh-TW'; +export type LanguageCode = 'zh-CN' | 'en-US' | 'zh-TW' | 'pt-BR'; export type Language = { code: LanguageCode; @@ -21,6 +23,7 @@ export const languages: Language[] = [ { code: 'en-US', label: 'English' }, { code: 'zh-CN', label: '简体中文' }, { code: 'zh-TW', label: '繁體中文' }, + { code: 'pt-BR', label: 'Português' }, ]; -export { zhCN, enUS, zhTW }; +export { zhCN, enUS, zhTW, ptBR }; diff --git a/frontend/src/lib/locales/pt-BR/index.ts b/frontend/src/lib/locales/pt-BR/index.ts new file mode 100644 index 0000000..31ff785 --- /dev/null +++ b/frontend/src/lib/locales/pt-BR/index.ts @@ -0,0 +1,951 @@ +export const ptBR = { + common: { + search: "Buscar...", + create: "Novo", + new: "Novo", + cancel: "Cancelar", + delete: "Excluir", + edit: "Editar", + theme: "Tema", + signOut: "Sair", + noMatches: "Nenhum resultado encontrado", + tryDifferentSearch: "Tente usar um termo de busca diferente.", + light: "Claro", + dark: "Escuro", + system: "Sistema", + loading: "Carregando...", + note: "Nota", + insight: "Insight", + newSource: "Nova Fonte", + newNotebook: "Novo Caderno", + newPodcast: "Novo Podcast", + language: "Idioma", + english: "English", + chinese: "简体中文", + source: "Fonte", + notebook: "Caderno", + podcast: "Podcast", + quickActions: "Ações rápidas", + quickActionsDesc: "Navegação, busca, perguntar, tema", + appName: "Open Notebook", + add: "Adicionar", + remove: "Remover", + confirm: "Confirmar", + warning: "Aviso", + error: "Erro", + success: "Sucesso", + sessions: "Sessões", + model: "Modelo", + send: "Enviar", + back: "Voltar", + next: "Próximo", + done: "Concluído", + processing: "Processando...", + creating: "Criando...", + tokenCount: "Tokens", + charCount: "Caracteres", + linked: "Vinculado", + added: "Adicionado em {date}", + adding: "Adicionando...", + addSelected: "Adicionar Selecionados", + customModel: "Modelo Personalizado", + messages: "Mensagens", + failed: "falhou", + current: "Atual", + save: "Salvar", + writeNote: "Escrever Nota", + batchMode: "Modo em Lote", + optional: "Opcional", + type: "Tipo", + title: "Título", + created: "Criado {time}", + updated: "Atualizado {time}", + actions: "Ações", + noResults: "Sem resultados", + references: "Referências", + refreshPage: "Por favor, tente atualizar a página", + refresh: "Atualizar", + aiGenerated: "Gerado por IA", + human: "Humano", + unknown: "Desconhecido", + notes: "Notas", + chat: "Chat", + details: "Detalhes", + deleteForever: "Excluir Permanentemente", + connectionError: "Erro de Conexão", + unableToConnect: "Não foi possível conectar ao servidor da API", + retryConnection: "Tentar Novamente", + diagnosticInfo: "Informações de Diagnóstico", + version: "Versão", + built: "Compilado", + apiUrl: "URL da API", + frontendUrl: "URL do Frontend", + checkConsoleLogs: "Verifique o console do navegador para logs detalhados (procure por mensagens 🔧 [Config])", + yes: "Sim", + no: "Não", + simple: "Simples", + saving: "Salvando...", + description: "Descrição", + saveToNote: "Salvar em nota", + copyToClipboard: "Copiar para área de transferência", + close: "Fechar", + insights: "Insights", + progress: "Progresso", + deleting: "Excluindo...", + created_label: "Criado", + updated_label: "Atualizado", + download: "Baixar", + saveChanges: "Salvar Alterações", + name: "Nome", + default: "Padrão", + nameRequired: "Nome é obrigatório", + modelConfiguration: "Configuração do Modelo", + resetToDefault: "Restaurar Padrão", + notFound: "Não encontrado", + reasoning: "Raciocínio", + searchTerms: "Termos de Busca", + strategy: "Estratégia", + individualAnswers: "Respostas Individuais ({count})", + finalAnswer: "Resposta Final", + notebookLabel: "Caderno: {name}", + itemNotFound: "Este {type} não foi encontrado", + accessibility: { + navigation: "Navegação", + transformationViews: "Visualizações de transformação", + searchKB: "Perguntar ou buscar na base de conhecimento", + enterQuestion: "Digite sua pergunta para a base de conhecimento", + enterSearch: "Digite sua busca", + searchKBBtn: "Buscar na base de conhecimento", + podcastViews: "Visualizações de podcast", + chatSessions: "Sessões de chat", + ytVideo: "Vídeo do YouTube", + askResponse: "Resposta da Consulta", + searchNotebooks: "Buscar cadernos", + }, + url: "URL", + errorDetails: "Detalhes do Erro", + editTransformation: "Editar Transformação", + comingSoon: "Em breve", + retry: "Tentar Novamente", + traditionalChinese: "繁體中文", + completed: "concluído", + saveSuccess: "Salvo com sucesso", + contextModes: { + off: "Não incluído no chat", + insights: "Apenas insights", + full: "Conteúdo completo", + clickToCycle: "Clique para alternar", + }, + clickToEdit: "Clique para editar", + portuguese: "Português", + }, + apiErrors: { + notebookNotFound: "Caderno não encontrado", + sourceNotFound: "Fonte não encontrada", + transformationNotFound: "Transformação não encontrada", + fileUploadFailed: "Falha no upload do arquivo", + urlRequired: "URL é obrigatória para tipo link", + contentRequired: "Conteúdo é obrigatório para tipo texto", + invalidSourceType: "Tipo de fonte inválido", + processingFailed: "Processamento falhou", + failedToQueue: "Falha ao enfileirar processamento", + invalidSortBy: "Campo de ordenação deve ser 'created' ou 'updated'", + invalidSortOrder: "Ordem deve ser 'asc' ou 'desc'", + accessDenied: "Acesso ao arquivo negado", + fileNotFoundOnServer: "Arquivo não encontrado no servidor", + searchFailed: "Busca falhou", + askFailed: "Consulta falhou", + pleaseEnterQuestion: "Por favor, digite uma pergunta", + pleaseConfigureModels: "Por favor, configure todos os modelos necessários", + failedToCreateSession: "Falha ao criar sessão", + failedToUpdateSession: "Falha ao atualizar sessão", + failedToDeleteSession: "Falha ao excluir sessão", + failedToSendMessage: "Falha ao enviar mensagem", + unauthorized: "Acesso não autorizado, verifique sua senha", + invalidPassword: "Senha inválida", + missingAuth: "Autenticação ausente", + embeddingModelRequired: "Este recurso requer um modelo de embedding. Configure um na seção Modelos.", + strategyModelNotFound: "Modelo de estratégia não encontrado", + answerModelNotFound: "Modelo de resposta não encontrado", + finalAnswerModelNotFound: "Modelo de resposta final não encontrado", + noAnswerGenerated: "Nenhuma resposta pôde ser gerada", + genericError: "Ocorreu um erro inesperado", + }, + connectionErrors: { + apiTitle: "Não foi possível conectar ao servidor da API", + apiDesc: "O servidor da API do Open Notebook não pôde ser alcançado", + dbTitle: "Falha na conexão com o banco de dados", + dbDesc: "O servidor da API está rodando, mas o banco de dados não está acessível", + troubleshooting: "Isso geralmente significa:", + apiUnreachable1: "O servidor da API não está rodando", + apiUnreachable2: "O servidor da API está rodando em um endereço diferente", + apiUnreachable3: "Problemas de conectividade de rede", + dbFailed1: "SurrealDB não está rodando", + dbFailed2: "Configurações de conexão do banco de dados estão incorretas", + dbFailed3: "Problemas de rede entre API e banco de dados", + quickFixes: "Soluções rápidas:", + setApiUrl: "Defina a variável de ambiente API_URL:", + checkSurreal: "Verifique se o SurrealDB está rodando:", + seeDocumentation: "Para instruções detalhadas de configuração, veja:", + docLink: "Documentação do Open Notebook", + showTechnical: "Mostrar Detalhes Técnicos", + attemptedUrl: "URL Tentada", + message: "Mensagem", + technicalDetails: "Detalhes Técnicos", + stackTrace: "Stack Trace", + retryLabel: "Tentar Conexão Novamente", + retryHint: "Pressione R ou clique no botão para tentar novamente", + dockerLabel: "Para Docker", + localDevLabel: "Para desenvolvimento local", + }, + auth: { + loginTitle: "Open Notebook", + loginDesc: "Digite sua senha para acessar o aplicativo", + passwordPlaceholder: "Senha", + signingIn: "Entrando...", + signIn: "Entrar", + unhandledError: "Erro não tratado durante login", + connectErrorHint: "Não foi possível conectar ao servidor. Verifique se a API está rodando.", + }, + navigation: { + collect: "Coletar", + process: "Processar", + create: "Criar", + manage: "Gerenciar", + sources: "Fontes", + notebooks: "Cadernos", + askAndSearch: "Perguntar e Buscar", + podcasts: "Podcasts", + models: "Modelos", + transformations: "Transformações", + transformation: "Transformação", + settings: "Configurações", + advanced: "Avançado", + nav: "Navegação", + language: "Alternar idioma", + theme: "Tema", + search: "Buscar", + ask: "Perguntar", + }, + notebooks: { + title: "Cadernos", + newNotebook: "Novo Caderno", + searchPlaceholder: "Buscar cadernos...", + archived: "Arquivado", + archive: "Arquivar", + unarchive: "Desarquivar", + deleteNotebook: "Excluir Caderno", + deleteNotebookDesc: "Tem certeza que deseja excluir este caderno? Esta ação não pode ser desfeita.", + activeNotebooks: "Cadernos Ativos", + archivedNotebooks: "Cadernos Arquivados", + emptyDescription: "Comece criando seu primeiro caderno para organizar sua pesquisa.", + noActiveNotebooks: "Nenhum caderno ativo", + noArchivedNotebooks: "Nenhum caderno arquivado", + notFound: "Caderno não encontrado", + notFoundDesc: "O caderno solicitado não existe.", + noDescription: "Sem descrição...", + updated: "Atualizado", + namePlaceholder: "Nome do caderno", + addDescription: "Adicionar descrição...", + noNotesYet: "Nenhuma nota ainda", + deleteNote: "Excluir Nota", + deleteNoteConfirm: "Tem certeza que deseja excluir esta nota? Esta ação não pode ser desfeita.", + noteCreatedSuccess: "Nota criada com sucesso", + failedToCreateNote: "Falha ao criar nota", + noteUpdatedSuccess: "Nota atualizada com sucesso", + failedToUpdateNote: "Falha ao atualizar nota", + noteDeletedSuccess: "Nota excluída com sucesso", + failedToDeleteNote: "Falha ao excluir nota", + createNew: "Criar Novo Caderno", + createNewDesc: "Digite um nome e uma descrição opcional para começar.", + descPlaceholder: "Adicione mais informações sobre este caderno aqui...", + createSuccess: "Caderno criado com sucesso", + updateSuccess: "Caderno atualizado com sucesso", + deleteSuccess: "Caderno excluído com sucesso", + }, + sources: { + title: "Fontes", + add: "Adicionar Fonte", + addNew: "Adicionar Nova Fonte", + addExisting: "Adicionar Fonte Existente", + empty: "Nenhuma fonte ainda", + emptyDesc: "Adicione sua primeira fonte para começar a construir sua base de conhecimento.", + delete: "Excluir Fonte", + deleteMsg: "Tem certeza que deseja excluir esta fonte? Esta ação não pode ser desfeita.", + statusPreparing: "Preparando", + statusQueued: "Na Fila", + statusProcessing: "Processando", + statusCompleted: "Concluído", + statusFailed: "Falhou", + statusPreparingDesc: "Preparando para processar", + statusQueuedDesc: "Aguardando processamento", + statusProcessingDesc: "Sendo processado", + statusCompletedDesc: "Processado com sucesso", + statusFailedDesc: "Processamento falhou", + failedToLoad: "Falha ao carregar fontes", + allSourcesDesc: "Veja todas as suas fontes aqui. Você pode adicionar novas fontes ou gerenciar as existentes.", + allSources: "Todas as Fontes", + insights: "Insights", + yes: "Sim", + no: "Não", + loadingMore: "Carregando mais...", + noSourcesYet: "Nenhuma fonte ainda", + allSourcesDescShort: "Veja todas as suas fontes aqui.", + cannotSaveNoteNoNotebook: "Não é possível salvar nota: ID do caderno não disponível", + createFirstSource: "Adicione sua primeira fonte para começar a construir sua base de conhecimento.", + deleteSourceConfirm: "Tem certeza que deseja excluir esta fonte?", + deleteConfirm: "Tem certeza que deseja excluir isto?", + deleteConfirmWithTitle: "Tem certeza que deseja excluir \"{title}\"?", + deleteSuccess: "Fonte excluída com sucesso. Nota: Para excluir o arquivo do armazenamento, você deve habilitar a opção \"excluir arquivo\" na página de configurações.", + failedToDelete: "Falha ao excluir fonte", + sourceQueued: "Fonte Enfileirada", + sourceQueuedDesc: "Fonte enviada para processamento em segundo plano. Você pode monitorar o progresso na lista de fontes.", + sourceAddedSuccess: "Fonte adicionada com sucesso", + failedToAddSource: "Falha ao adicionar fonte", + sourceUpdatedSuccess: "Fonte atualizada com sucesso", + failedToUpdateSource: "Falha ao atualizar fonte", + sourceDeletedSuccess: "Fonte excluída com sucesso", + failedToDeleteSource: "Falha ao excluir fonte", + fileUploadedSuccess: "Arquivo enviado com sucesso", + failedToUploadFile: "Falha ao enviar arquivo", + sourceRequeued: "Fonte Reenfileirada", + sourceRequeuedDesc: "A fonte foi reenfileirada para processamento.", + failedToRetry: "Falha ao Tentar Novamente", + failedToRetryDesc: "Falha ao tentar processar fonte novamente. Por favor, tente de novo.", + sourcesAddedToNotebook: "{count} fonte(s) adicionada(s) ao caderno", + failedToAddSourcesToNotebook: "Falha ao adicionar fontes ao caderno", + partialAddSuccess: "{success} fonte(s) adicionada(s), {failed} falhou(aram)", + sourceRemovedFromNotebook: "Fonte removida do caderno com sucesso", + failedToRemoveSourceFromNotebook: "Falha ao remover fonte do caderno", + removeConfirm: "Tem certeza que deseja remover isto do caderno?", + checking: "Verificando...", + untitledSource: "Fonte Sem Título", + maxItems: "máx {count}", + insightsCount: "{count} insights", + details: "Detalhes", + detailsTitle: "Detalhes da Fonte", + content: "Conteúdo", + metadata: "Metadados", + type: { + link: "Link", + file: "Arquivo", + text: "Texto", + }, + id: "ID da Fonte", + topics: "Tópicos", + embedded: "Incorporado", + notEmbedded: "Não Incorporado", + embedContent: "Incorporar Conteúdo", + embedding: "Incorporando...", + alreadyEmbedded: "Já Incorporado", + downloadFile: "Baixar Arquivo", + fileUnavailable: "Arquivo indisponível", + preparing: "Preparando...", + generateNewInsight: "Gerar Novo Insight", + selectTransformation: "Selecione uma transformação...", + noInsightsYet: "Nenhum insight ainda", + createFirstInsight: "Crie seu primeiro insight usando uma transformação acima", + viewInsight: "Ver Insight", + deleteInsight: "Excluir Insight", + deleteInsightConfirm: "Tem certeza que deseja excluir este insight? Esta ação não pode ser desfeita.", + deleteNoteConfirm: "Tem certeza que deseja excluir esta nota? Esta ação não pode ser desfeita.", + editNote: "Editar nota", + createNote: "Criar nota", + addTitle: "Adicionar título...", + untitledNote: "Nota Sem Título", + writeNotePlaceholder: "Escreva o conteúdo da sua nota aqui...", + saveNote: "Salvar Nota", + createNoteBtn: "Criar Nota", + noNotesYet: "Nenhuma nota ainda", + createFirstNote: "Crie sua primeira nota para capturar insights e observações.", + deleteNote: "Excluir Nota", + urlLabel: "URL(s) *", + fileLabel: "Arquivo(s) *", + textContentLabel: "Conteúdo de Texto *", + enterUrlsPlaceholder: "Digite as URLs, uma por linha\nhttps://exemplo.com/artigo1\nhttps://exemplo.com/artigo2", + batchUrlHint: "Cole múltiplas URLs (uma por linha) para importação em lote", + invalidUrlsDetected: "URLs inválidas detectadas:", + lineLabel: "Linha {line}", + fixInvalidUrls: "Por favor, corrija ou remova as URLs inválidas para continuar", + selectMultipleFilesHint: "Selecione múltiplos arquivos para importação em lote. Suportados: Documentos (PDF, DOC, DOCX, PPT, XLS, EPUB, TXT, MD), Mídia (MP4, MP3, WAV, M4A), Imagens (JPG, PNG), Arquivos (ZIP)", + selectedFiles: "Arquivos selecionados:", + textPlaceholder: "Cole ou digite seu conteúdo aqui...", + titlePlaceholder: "Dê um título descritivo para sua fonte", + batchTitlesAuto: "Os títulos serão gerados automaticamente para cada fonte.", + batchCommonSettings: "Os mesmos cadernos e transformações serão aplicados a todos os itens.", + urlsCount: "{count} URL(s)", + filesCount: "{count} arquivo(s)", + addSource: "Adicionar Fonte", + notEmbeddedAlert: "Conteúdo Não Incorporado", + notEmbeddedDesc: "Este conteúdo não foi incorporado para busca vetorial. A incorporação habilita recursos avançados de busca e melhor descoberta de conteúdo.", + openOnYoutube: "Abrir no YouTube", + urlCopied: "URL copiada para área de transferência", + viewSource: "Ver Fonte", + noInsightSelected: "Nenhum insight selecionado", + sourceInsight: "Insight da Fonte", + manageNotebooks: "Gerenciar Cadernos", + manageNotebooksDesc: "Gerencie quais cadernos contêm esta fonte", + noNotebooksAvailable: "Nenhum caderno disponível", + loadFailed: "Falha ao carregar detalhes da fonte", + removeFromNotebook: "Remover do Caderno", + retryProcessing: "Tentar Processamento Novamente", + deleteSource: "Excluir Fonte", + retry: "Tentar Novamente", + progress: "Progresso", + addExistingTitle: "Adicionar Fontes Existentes", + addExistingDesc: "Selecione fontes existentes de todos os seus cadernos para adicionar ao atual.", + searchPlaceholder: "Buscar fontes por nome ou URL...", + noNotebooksFound: "Nenhum caderno encontrado.", + showingFirst100: "Mostrando as primeiras 100 fontes. Use a busca para encontrar específicas.", + selectedCount: "{count} fontes selecionadas", + added: "Adicionado em {date}", + addUrl: "Adicionar URL", + uploadFile: "Enviar Arquivo", + enterText: "Inserir Texto", + processDescription: "O conteúdo será processado e analisado por IA.", + processingFiles: "Processando seus arquivos...", + titleRequired: "Um título é obrigatório para conteúdo de texto", + titleGenerated: "Se deixado vazio, um título será gerado a partir do conteúdo", + batchCount: "{count} {type} serão processados", + enableEmbedding: "Habilitar incorporação para busca", + embeddingDesc: "Permite que esta fonte seja encontrada em buscas vetoriais e consultas de IA", + embeddingAlways: "Incorporação habilitada automaticamente", + embeddingAlwaysDesc: "Suas configurações estão definidas para sempre incorporar conteúdo para busca vetorial.", + embeddingNever: "Incorporação desabilitada", + embeddingNeverDesc: "Suas configurações estão definidas para pular incorporação. Busca vetorial não estará disponível para esta fonte.", + changeInSettings: "Você pode alterar isso em Configurações", + notFound: "Fonte não encontrada", + noContent: "Nenhum conteúdo disponível", + insightsDesc: "Insights gerados a partir da análise do modelo", + uploadedFile: "Arquivo enviado", + fileUnavailableDesc: "Este arquivo está indisponível no momento por razões do sistema de armazenamento.", + batchSuccess: "{count} fonte(s) criada(s) com sucesso", + batchFailed: "Falha ao criar todas as {count} fontes", + batchPartial: "{success} sucesso, {failed} falhou(aram)", + submittingSource: "Enviando fonte para processamento...", + contentRequired: "Por favor, forneça o conteúdo necessário para o tipo de fonte selecionado", + titleRequiredForText: "Título é obrigatório para fontes de texto", + processingBatchSources: "Processando {count} fontes. Isso pode levar alguns momentos.", + processingSource: "Sua fonte está sendo processada. Isso pode levar alguns momentos.", + maxFilesAllowed: "Máximo de {count} arquivos permitidos por lote", + }, + chat: { + sessions: "Sessões", + sessionTitlePlaceholder: "Digite um título aqui...", + noSessions: "Nenhuma sessão de chat ainda", + startChatting: "Comece a conversar sobre suas fontes.", + deleteSession: "Excluir Sessão", + deleteSessionDesc: "Tem certeza que deseja excluir esta sessão de chat? Esta ação não pode ser desfeita.", + sendPlaceholder: "Pergunte qualquer coisa sobre suas fontes...", + newChat: "Novo Chat", + sessionsTitle: "Sessões de Chat", + clearhistory: "Limpar Histórico", + renameSession: "Renomear Sessão", + noSourcesLinked: "Nenhuma fonte vinculada", + thinking: "IA está pensando...", + chatWith: "Conversar com {name}", + startConversation: "Inicie uma conversa sobre este {type}", + askQuestions: "Faça perguntas para entender melhor o conteúdo", + pressToSend: "Pressione {key} para enviar", + model: "Modelo", + createToStart: "Crie uma sessão para começar.", + chatWithNotebook: "Conversar com Caderno", + unableToLoadChat: "Não foi possível carregar o chat", + noDescription: "Sem descrição", + startByCreating: "Comece criando seu primeiro caderno para organizar sua pesquisa.", + messagesCount: "{count} mensagens", + sessionCreated: "Sessão de chat criada", + sessionUpdated: "Sessão atualizada", + sessionDeleted: "Sessão excluída", + }, + searchPage: { + askAndSearch: "Perguntar e Buscar", + chooseAMode: "Escolha um modo", + askBeta: "Perguntar (beta)", + search: "Buscar", + askYourKb: "Pergunte à Sua Base de Conhecimento (beta)", + askYourKbDesc: "O LLM responderá sua consulta com base nos documentos da sua base de conhecimento.", + question: "Pergunta", + enterQuestionPlaceholder: "Digite sua pergunta...", + pressToSubmit: "Pressione Cmd/Ctrl+Enter para enviar", + noEmbeddingModel: "Você não pode usar este recurso porque não tem um modelo de embedding selecionado. Configure um na página de Modelos.", + usingCustomModels: "Usando Modelos Personalizados", + usingDefaultModels: "Usando Modelos Padrão", + advanced: "Avançado", + strategy: "Estratégia", + answer: "Resposta", + final: "Final", + ask: "Perguntar", + processing: "Processando...", + saveToNotebooks: "Salvar em Cadernos", + searchDesc: "Busque em sua base de conhecimento por palavras-chave ou conceitos específicos", + enterSearchPlaceholder: "Digite sua busca...", + pressToSearch: "Pressione Enter para buscar", + searchType: "Tipo de Busca", + vectorSearchWarning: "Busca vetorial requer um modelo de embedding. Apenas busca por texto está disponível.", + textSearch: "Busca por Texto", + vectorSearch: "Busca Vetorial", + searchIn: "Buscar Em", + searchSources: "Buscar Fontes", + searchNotes: "Buscar Notas", + resultsFound: "{count} resultados encontrados", + matches: "Correspondências ({count})", + noResultsFor: "Nenhum resultado encontrado para \"{query}\"", + notSet: "Não definido", + saveToNotebook: "Salvar no Caderno", + saveSuccess: "Salvo no caderno com sucesso", + saveError: "Falha ao salvar no caderno", + selectNotebook: "Selecionar Caderno", + createNewNotebook: "Criar Novo Caderno", + cancel: "Cancelar", + searchAndAsk: "Buscar e Perguntar", + searchResultsFor: "Resultados da busca para \"{query}\"", + askAbout: "Perguntar sobre \"{query}\"", + orSearchKb: "Ou busque em sua base de conhecimento", + saving: "Salvando...", + advancedModelTitle: "Seleção Avançada de Modelo", + advancedModelDesc: "Escolha modelos específicos para cada etapa do processo de Perguntar", + strategyModel: "Modelo de Estratégia", + answerModel: "Modelo de Resposta", + finalAnswerModel: "Modelo de Resposta Final", + selectStrategyPlaceholder: "Selecione o modelo de estratégia", + selectAnswerPlaceholder: "Selecione o modelo de resposta", + selectFinalPlaceholder: "Selecione o modelo de resposta final", + saveChanges: "Salvar Alterações", + processingQuestion: "Processando sua pergunta...", + }, + podcasts: { + podcastProfiles: { + business_analysis: "Análise de Negócios", + solo_expert: "Especialista Solo", + tech_discussion: "Discussão Tech", + }, + generateEpisode: "Gerar Episódio de Podcast", + generateEpisodeDesc: "Selecione o conteúdo a incluir e configure os detalhes do episódio antes de gerar um novo episódio de podcast.", + content: "Conteúdo", + contentDesc: "Escolha cadernos, fontes e notas para incluir neste episódio.", + itemsSelected: "{count} itens selecionados", + tokens: "{count} tokens", + chars: "{count} caracteres", + loadingNotebooks: "Carregando cadernos...", + noNotebooksFoundInPodcasts: "Nenhum caderno encontrado. Crie um caderno e adicione conteúdo antes de gerar um podcast.", + noContentSelected: "Nenhum conteúdo selecionado", + summary: "Resumo", + fullContent: "Conteúdo completo", + untitledSource: "Fonte sem título", + untitledNote: "Nota sem título", + episodeSettings: "Configurações do Episódio", + episodeProfile: "Perfil do episódio", + episodeProfilePlaceholder: "Selecione um perfil de episódio", + episodeName: "Nome do episódio", + episodeNamePlaceholder: "ex., IA e o Futuro do Trabalho", + additionalInstructions: "Instruções adicionais", + instructionsPlaceholder: "Qualquer conselho suplementar para adicionar ao briefing do episódio...", + generating: "Gerando...", + generate: "Gerar", + hostPlaceholder: "Apresentador {number}", + profileRequired: "Perfil de Episódio Necessário", + profileRequiredDesc: "Selecione um perfil de episódio antes de gerar um podcast.", + nameRequired: "Nome do episódio necessário", + nameRequiredDesc: "Forneça um nome para o episódio.", + addContext: "Adicionar contexto", + addContextDesc: "Selecione pelo menos uma fonte ou nota para incluir no episódio.", + generationFailed: "Geração do podcast falhou", + speakerProfile: "Perfil do Locutor", + usesSpeakerProfile: "Usa perfil de locutor", + sources: "Fontes", + notes: "Notas", + noSources: "Nenhuma fonte disponível neste caderno.", + noNotes: "Nenhuma nota disponível neste caderno.", + selectMode: "Selecionar modo", + buildContextFailed: "Falha ao construir contexto. Por favor, revise suas seleções.", + podcastTaskStarted: "Tarefa de podcast iniciada", + loadingProfiles: "Carregando perfis de episódio...", + noProfilesFound: "Nenhum perfil de episódio encontrado. Crie um perfil de episódio antes de gerar um podcast.", + listTitle: "Podcasts", + listDesc: "Acompanhe episódios gerados e gerencie templates reutilizáveis.", + chooseAView: "Escolha uma visualização", + episodesTab: "Episódios", + templatesTab: "Templates", + overviewTitle: "Visão geral dos episódios", + overviewDesc: "Monitore trabalhos de geração de podcast e revise os artefatos finais.", + generateBtn: "Gerar Podcast", + total: "Total", + processingLabel: "Processando", + completedLabel: "Concluídos", + failedLabel: "Falharam", + pendingLabel: "Pendentes", + loadErrorTitle: "Falha ao carregar episódios", + loadErrorDesc: "Não foi possível buscar os episódios de podcast mais recentes. Tente novamente em breve.", + loadingEpisodes: "Carregando episódios…", + noEpisodesYet: "Nenhum episódio de podcast ainda. Gere seu primeiro a partir das interfaces de chat de caderno ou fonte.", + statusRunningTitle: "Processando Atualmente", + statusRunningDesc: "Episódios que estão gerando ativos ativamente.", + statusPendingTitle: "Na Fila / Pendentes", + statusPendingDesc: "Episódios enviados aguardando início do processamento.", + statusCompletedTitle: "Episódios Concluídos", + statusCompletedDesc: "Prontos para revisar, baixar ou publicar.", + statusFailedTitle: "Episódios com Falha", + statusFailedDesc: "Episódios que encontraram problemas durante a geração.", + templatesWorkspaceTitle: "Área de trabalho de templates", + templatesWorkspaceDesc: "Construa configurações de episódio e locutor reutilizáveis para produção rápida de podcasts.", + howTemplatesPowerTitle: "Como os templates potencializam a geração de podcasts", + howTemplatesPowerDesc: "Templates dividem o fluxo de trabalho do podcast em dois blocos de construção reutilizáveis. Misture e combine-os sempre que gerar um novo episódio.", + episodeProfilesSetFormat: "Perfis de episódio definem o formato", + episodeProfilesList1: "Delineiam o número de segmentos e como a história flui", + episodeProfilesList2: "Escolhem os modelos de linguagem usados para briefing, outline e escrita do roteiro", + episodeProfilesList3: "Armazenam briefings padrão para que cada episódio comece com um tom consistente", + speakerProfilesBringVoices: "Perfis de locutor dão vida às vozes", + speakerProfilesList1: "Escolhem o provedor e modelo de text-to-speech", + speakerProfilesList2: "Capturam personalidade, história e notas de pronúncia por locutor", + speakerProfilesList3: "Reutilizam as mesmas vozes de apresentador ou convidado em diferentes formatos de episódio", + recommendedWorkflow: "Fluxo de trabalho recomendado", + workflowStep1: "Crie perfis de locutor para cada voz que você precisa", + workflowStep2: "Construa perfis de episódio que referenciam esses locutores pelo nome", + workflowStep3: "Gere podcasts selecionando o perfil de episódio que se encaixa na história", + workflowHint: "Perfis de episódio referenciam perfis de locutor pelo nome, então começar com locutores evita atribuições de voz faltantes depois.", + failedToLoadTemplates: "Falha ao carregar dados de templates", + failedToLoadTemplatesDesc: "Certifique-se de que a API está rodando e tente novamente. Algumas seções podem estar incompletas.", + loadingTemplates: "Carregando templates…", + speakerProfilesTitle: "Perfis de locutor", + speakerProfilesDesc: "Configure vozes e personalidades para episódios gerados.", + createSpeaker: "Criar locutor", + noSpeakerProfiles: "Nenhum perfil de locutor ainda. Crie um para disponibilizar templates de episódio.", + noDescription: "Nenhuma descrição fornecida.", + usedByCount_one: "Usado por 1 episódio", + usedByCount_other: "Usado por {count} episódios", + usedByCount: "Usado por {count} episódios", + unused: "Não utilizado", + voiceId: "ID da Voz", + backstory: "História", + personality: "Personalidade", + edit: "Editar", + duplicate: "Duplicar", + deleteSpeakerProfileTitle: "Excluir perfil de locutor?", + deleteSpeakerProfileDesc: "Excluir \"{name}\" não pode ser desfeito.", + deleteSpeakerDisabledHint: "Remova este locutor dos perfis de episódio antes de excluí-lo.", + deleting: "Excluindo…", + episodeProfilesTitle: "Perfis de episódio", + episodeProfilesDesc: "Defina configurações de geração reutilizáveis para seus programas.", + createProfile: "Criar perfil", + createSpeakerFirst: "Crie um perfil de locutor antes de adicionar um perfil de episódio.", + noEpisodeProfiles: "Nenhum perfil de episódio ainda. Crie um para iniciar a geração de podcasts.", + speakerCreated: "Locutor Criado", + speakerCreatedDesc: "O locutor \"{name}\" foi adicionado com sucesso.", + failedToCreateSpeaker: "Falha ao criar perfil de locutor", + speakerUpdated: "Locutor Atualizado", + speakerUpdatedDesc: "O locutor \"{name}\" foi atualizado com sucesso.", + failedToUpdateSpeaker: "Falha ao atualizar perfil de locutor", + speakerDeleted: "Locutor Excluído", + speakerDeletedDesc: "O locutor \"{name}\" foi removido com sucesso.", + failedToDeleteSpeaker: "Falha ao excluir perfil de locutor", + speakerDuplicated: "Locutor Duplicado", + speakerDuplicatedDesc: "O locutor \"{name}\" foi duplicado com sucesso.", + failedToDuplicateSpeaker: "Falha ao duplicar perfil de locutor", + generationStarted: "Geração Iniciada", + generationStartedDesc: "A geração do podcast foi enfileirada.", + failedToStartGeneration: "Falha ao iniciar geração", + tryAgainMoment: "Por favor, tente novamente em um momento.", + deleteProfileTitle: "Excluir perfil?", + deleteProfileDesc: "Isso removerá \"{name}\". Episódios existentes mantêm seus dados, mas novos não usarão mais esta configuração.", + profileCreated: "Perfil Criado", + profileCreatedDesc: "O perfil de episódio \"{name}\" foi criado com sucesso.", + failedToCreateProfile: "Falha ao criar perfil", + profileUpdated: "Perfil Atualizado", + profileUpdatedDesc: "O perfil de episódio \"{name}\" foi atualizado com sucesso.", + failedToUpdateProfile: "Falha ao atualizar perfil", + profileDeleted: "Perfil Excluído", + profileDeletedDesc: "O perfil de episódio \"{name}\" foi removido com sucesso.", + failedToDeleteProfile: "Falha ao excluir perfil", + failedToDeleteProfileDesc: "Falha ao remover o perfil de episódio.", + profileDuplicated: "Perfil Duplicado", + profileDuplicatedDesc: "O perfil de episódio \"{name}\" foi duplicado com sucesso.", + failedToDuplicateProfile: "Falha ao duplicar perfil", + episodeDeleted: "Episódio Excluído", + episodeDeletedDesc: "O episódio foi excluído com sucesso.", + failedToDeleteEpisode: "Falha ao excluir episódio", + failedToDeleteSpeakerDesc: "Falha ao remover o perfil de locutor.", + outlineModel: "Modelo de outline", + transcriptModel: "Modelo de transcrição", + segments: "Segmentos", + defaultBriefingTitle: "Briefing padrão", + created: "Criado em {time}", + details: "Detalhes", + summaryTab: "Resumo", + outlineTab: "Outline", + transcriptTab: "Transcrição", + briefing: "Briefing", + noOutline: "Nenhum outline disponível.", + noTranscript: "Nenhuma transcrição disponível.", + deleteEpisodeTitle: "Excluir episódio?", + deleteEpisodeDesc: "Isso removerá \"{name}\" e seu arquivo de áudio permanentemente.", + audioUnavailable: "Áudio indisponível", + segment: "Segmento", + speaker: "Locutor", + profile: "Perfil", + link: "Link", + file: "Arquivo", + embedded: "Incorporado", + notEmbedded: "Não incorporado", + noSpeakerProfilesAvailable: "Nenhum perfil de locutor disponível", + noLanguageModelsAvailable: "Nenhum modelo de linguagem disponível", + editEpisodeProfile: "Editar Perfil de Episódio", + createEpisodeProfile: "Criar Perfil de Episódio", + episodeProfileFormDesc: "Defina como os episódios devem ser gerados e qual configuração de locutor usar por padrão.", + noSpeakerProfilesDesc: "Crie um perfil de locutor antes de configurar um perfil de episódio.", + noLanguageModelsDesc: "Adicione modelos de linguagem na seção Modelos para configurar a geração de outline e transcrição.", + profileName: "Nome do perfil", + profileNamePlaceholder: "ex., Discussão tech", + descriptionPlaceholder: "Breve resumo de quando usar este perfil", + speakerConfig: "Configuração de locutor", + selectSpeakerProfile: "Selecione um perfil de locutor", + outlineGeneration: "Geração de outline", + transcriptGeneration: "Geração de transcrição", + defaultBriefingPlaceholder: "Delineie a estrutura, tom e objetivos para este formato de episódio", + editSpeakerProfile: "Editar Perfil de Locutor", + createSpeakerProfile: "Criar Perfil de Locutor", + speakerProfileFormDesc: "Configure as configurações de text-to-speech e defina até quatro locutores.", + noTtsModelsAvailable: "Nenhum modelo de text-to-speech disponível", + noTtsModelsDesc: "Adicione modelos TTS na seção Modelos antes de criar um perfil de locutor.", + speakers: "Locutores", + speakersDesc: "Configure entre uma e quatro vozes para este perfil.", + addSpeaker: "Adicionar locutor", + speakerNumber: "Locutor {number}", + backstoryPlaceholder: "Breve biografia ou contexto para o locutor", + personalityPlaceholder: "Descreva estilo e tom", + outlineProviderRequired: "Provedor de outline é obrigatório", + outlineModelRequired: "Modelo de outline é obrigatório", + transcriptProviderRequired: "Provedor de transcrição é obrigatório", + transcriptModelRequired: "Modelo de transcrição é obrigatório", + defaultBriefingRequired: "Briefing padrão é obrigatório", + segmentsInteger: "Deve ser um número inteiro", + segmentsMin: "Mínimo de 3 segmentos", + segmentsMax: "Máximo de 20 segmentos", + voiceIdRequired: "ID da voz é obrigatório", + backstoryRequired: "História é obrigatória", + personalityRequired: "Personalidade é obrigatória", + speakerCountMin: "Pelo menos um locutor é necessário", + speakerCountMax: "Você pode configurar até 4 locutores", + delete: "Excluir", + unknown: "Desconhecido", + deleteSuccess: "Podcast excluído com sucesso", + failedToDelete: "Falha ao excluir podcast", + }, + settings: { + contentProcessing: "Processamento de Conteúdo", + contentProcessingDesc: "Configure como documentos e URLs são processados", + docEngine: "Motor de Processamento de Documentos", + docEnginePlaceholder: "Selecione o motor de processamento de documentos", + urlEngine: "Motor de Processamento de URL", + urlEnginePlaceholder: "Selecione o motor de processamento de URL", + autoRecommended: "Auto (Recomendado)", + simple: "Simples", + docling: "Docling", + helpMeChoose: "Ajude-me a escolher", + docHelp: "· Docling é um pouco mais lento, mas mais preciso, especialmente se os documentos contêm tabelas e imagens. · Simples extrairá qualquer conteúdo do documento sem formatá-lo. · Auto (recomendado) tentará processar através do docling e usará simples como fallback.", + firecrawl: "Firecrawl", + jina: "Jina", + urlHelp: "· Firecrawl é um serviço pago (com tier gratuito), e muito poderoso. · Jina também é uma boa opção e também tem um tier gratuito. · Simples usará extração HTTP básica e perderá conteúdo em sites baseados em javascript. · Auto (recomendado) tentará usar firecrawl, depois Jina, e finalmente fallback para simples.", + embeddingAndSearch: "Embedding e Busca", + embeddingAndSearchDesc: "Configure opções de busca e embedding", + defaultEmbeddingOption: "Opção Padrão de Embedding", + embeddingOptionPlaceholder: "Selecione a opção de embedding", + ask: "Perguntar", + always: "Sempre", + never: "Nunca", + embeddingHelp: "Incorporar o conteúdo facilitará encontrá-lo por você e seus agentes de IA. Se você está rodando um modelo de embedding local (Ollama, por exemplo), não precisa se preocupar com custo e pode incorporar tudo.", + fileManagement: "Gerenciamento de Arquivos", + fileManagementDesc: "Configure opções de manipulação e armazenamento de arquivos", + autoDeleteFiles: "Excluir Arquivos Automaticamente", + autoDeletePlaceholder: "Selecione a opção de exclusão automática", + filesHelp: "Uma vez que seus arquivos são enviados e processados, eles não são mais necessários. A maioria dos usuários deve permitir que o Open Notebook exclua arquivos enviados da pasta de upload automaticamente.", + loadFailed: "Falha ao carregar configurações", + }, + advanced: { + title: "Ferramentas Avançadas", + desc: "Ferramentas e utilitários avançados para usuários avançados", + systemInfo: "Informações do Sistema", + systemInfoDesc: "Visualize o status dos componentes subjacentes do sistema", + rebuildEmbeddings: "Reconstruir Embeddings", + rebuildEmbeddingsDesc: "Reconstruir índice de busca vetorial para todas as fontes", + rebuildWarning: "Esta ação pode ser muito demorada dependendo do número de fontes que você tem. Ela limpará os índices vetoriais existentes e regerará embeddings para tudo.", + startRebuild: "Iniciar Reconstrução", + rebuilding: "Reconstruindo...", + rebuildSuccess: "Reconstrução de embedding iniciada com sucesso", + currentVersion: "Versão Atual", + latestVersion: "Última Versão", + status: "Status", + updateAvailable: "Versão {version} Disponível", + updateAvailableDesc: "Uma nova versão do Open Notebook está disponível.", + upToDate: "Atualizado", + unknown: "Desconhecido", + viewOnGithub: "Ver no GitHub", + updateCheckFailed: "Não foi possível verificar atualizações. O GitHub pode estar inacessível.", + rebuild: { + mode: "Modo de Reconstrução", + existing: "Existentes", + all: "Todos", + existingDesc: "Re-incorporar apenas itens que já têm embeddings (mais rápido, para troca de modelo)", + allDesc: "Re-incorporar itens existentes + criar embeddings para itens sem nenhum (mais lento, abrangente)", + include: "Incluir na Reconstrução", + selectOneError: "Por favor, selecione pelo menos um tipo de item para reconstruir", + starting: "Iniciando Reconstrução...", + startBtn: "🚀 Iniciar Reconstrução", + queued: "Na Fila", + running: "Executando...", + completed: "Concluído!", + failed: "Falhou", + leavePageHint: "Você pode sair desta página pois isso será executado em segundo plano", + startNew: "Iniciar Nova Reconstrução", + itemsProcessed: "{processed}/{total} itens ({percent}%)", + failedItems: "{count} itens falharam ao processar", + time: "Tempo", + whenToRebuild: "Quando devo reconstruir embeddings?", + whenToRebuildAns: "Você deve reconstruir ao trocar modelos, atualizar versões, corrigir corrupção ou após importações em massa.", + howLong: "Quanto tempo leva a reconstrução?", + howLongAns: "O tempo de processamento depende da quantidade de itens, velocidade do modelo e limites de taxa da API. Modelos locais geralmente são muito rápidos.", + isSafe: "É seguro reconstruir enquanto usa o aplicativo?", + isSafeAns: "Sim, reconstruir é seguro! Não exclui conteúdo, apenas substitui embeddings, e lida com erros graciosamente.", + }, + }, + transformations: { + title: "Transformações", + desc: "Transformações são prompts que serão usados pelo LLM para processar uma fonte e extrair insights, resumos, etc.", + workspace: "Escolha um espaço de trabalho", + playground: "Playground", + defaultPrompt: "Prompt de Transformação Padrão", + defaultPromptDesc: "Isso será adicionado a todos os seus prompts de transformação", + defaultPromptPlaceholder: "Digite suas instruções padrão de transformação...", + saveDefault: "Salvar Padrão", + listTitle: "Transformações Personalizadas", + createNew: "Criar Nova", + testInPlayground: "Testar no Playground", + inputLabel: "Texto de Entrada", + inputPlaceholder: "Digite algum texto para transformar...", + outputLabel: "Saída", + runTest: "Executar Transformação", + running: "Executando...", + selectToStart: "Selecione uma transformação para começar", + name: "Nome", + namePlaceholder: "Identificador único, ex. topicos_principais", + titlePlaceholder: "Título exibido, usa o nome por padrão", + promptPlaceholder: "Escreva o prompt que vai alimentar esta transformação...", + descriptionPlaceholder: "Descreva o que esta transformação faz.", + suggestDefault: "Sugerir por padrão em novas fontes", + promptHint: "Prompts devem ser escritos com o conteúdo da fonte em mente. Você pode pedir ao modelo para resumir, extrair insights ou produzir saídas estruturadas como tabelas.", + createSuccess: "Transformação criada com sucesso", + updateSuccess: "Transformação atualizada com sucesso", + deleteSuccess: "Transformação excluída com sucesso", + noTransformations: "Nenhuma transformação ainda", + createOne: "Crie uma transformação para começar", + deleteDesc: "Excluir esta transformação não pode ser desfeito.", + selectModel: "Selecione um modelo", + deleteConfirm: "Tem certeza que deseja excluir esta transformação?", + model: "Modelo", + systemPrompt: "Prompt do Sistema", + type: "Tipo", + extraction: "Extração", + summary: "Resumo", + custom: "Personalizado", + saveChanges: "Salvar Alterações", + overrideModelDesc: "Substitua o modelo padrão para esta sessão de chat. Deixe vazio para usar o padrão do sistema.", + sessionUseReplacement: "Esta sessão usará {name} em vez do modelo padrão.", + systemDefault: "Padrão do Sistema", + }, + models: { + title: "Gerenciamento de Modelos", + desc: "Configure modelos de IA para diferentes propósitos no Open Notebook", + failedToLoad: "Falha ao carregar dados dos modelos", + language: "Modelos de Linguagem", + embedding: "Modelos de Embedding", + tts: "Text to Speech (TTS)", + stt: "Speech to Text (STT)", + providers: "Provedores", + defaultModels: "Modelos Padrão", + status: "Status", + notConfigured: "Não configurado", + active: "Ativo", + inactive: "Inativo", + configure: "Configurar", + saveChanges: "Salvar Alterações", + addModel: "Adicionar Modelo", + modelName: "Nome do Modelo", + provider: "Provedor", + apiKey: "Chave da API", + baseUrl: "URL Base", + capabilities: "Capacidades", + enabled: "Habilitado", + disabled: "Desabilitado", + deleteConfirm: "Tem certeza que deseja excluir este modelo?", + deleteSuccess: "Modelo excluído com sucesso", + saveSuccess: "Modelo salvo com sucesso", + providerStatus: "Status do Provedor", + connectionOk: "Conexão OK", + connectionFailed: "Conexão falhou", + changeEmbeddingWarning: "Alterar o modelo de embedding padrão afetará novas fontes. Fontes existentes podem precisar ser reindexadas.", + changeEmbeddingTitle: "Alterar Modelo de Embedding Padrão?", + aiProviders: "Provedores de IA", + providerConfigDesc: "Configure provedores através de variáveis de ambiente para habilitar seus modelos.", + configuredCount: "{count} de {total} configurados", + noModels: "Sem modelos", + learnMore: "Saiba como configurar provedores →", + seeLess: "Ver menos", + seeAll: "Ver todos os {count} provedores", + language_models: "Modelos de Linguagem", + embedding_models: "Modelos de Embedding", + text_to_speech: "Text to Speech (TTS)", + speech_to_text: "Speech to Text (STT)", + languageDesc: "Chat, transformações e geração de texto", + embeddingDesc: "Busca semântica e embeddings vetoriais", + ttsDesc: "Gerar áudio a partir de texto", + sttDesc: "Transcrever áudio para texto", + all: "Todos", + noModelsConfigured: "Nenhum modelo configurado", + noProviderModelsConfigured: "Nenhum modelo {provider} configurado", + showMore: "Mostrar mais {count}", + deleteModel: "Excluir Modelo", + deleteModelDesc: "Tem certeza que deseja excluir \"{name}\"? Esta ação não pode ser desfeita.", + defaultAssignments: "Atribuições de Modelo Padrão", + defaultAssignmentsDesc: "Configure quais modelos usar para diferentes propósitos no Open Notebook", + missingRequiredModels: "Modelos obrigatórios ausentes: {models}. O Open Notebook pode não funcionar corretamente sem eles.", + selectModelPlaceholder: "Selecione um modelo", + requiredModelPlaceholder: "⚠️ Obrigatório - Selecione um modelo", + whichModelToChoose: "Qual modelo devo escolher? →", + chatModelLabel: "Modelo de Chat", + chatModelDesc: "Usado para conversas de chat", + transformationModelLabel: "Modelo de Transformação", + transformationModelDesc: "Usado para resumos, insights e transformações", + toolsModelLabel: "Modelo de Ferramentas", + toolsModelDesc: "Usado para chamadas de função - OpenAI ou Anthropic recomendado", + largeContextModelLabel: "Modelo de Contexto Grande", + largeContextModelDesc: "Usado para processar documentos grandes - Gemini recomendado", + embeddingModelLabel: "Modelo de Embedding", + embeddingModelDesc: "Usado para busca semântica e embeddings vetoriais", + ttsModelLabel: "Modelo Text-to-Speech", + ttsModelDesc: "Usado para geração de podcast", + sttModelLabel: "Modelo Speech-to-Text", + sttModelDesc: "Usado para transcrição de áudio", + addSpecificModel: "Adicionar Modelo de {type}", + addSpecificModelDesc: "Configure um novo modelo de {type} dos provedores disponíveis.", + noProvidersForType: "Nenhum provedor disponível para modelos de {type}", + selectProviderPlaceholder: "Selecione um provedor", + providerRequired: "Provedor é obrigatório", + modelNameRequired: "Nome do modelo é obrigatório", + modelRequired: "Modelo é obrigatório", + adding: "Adicionando...", + azureHint: "Para Azure, use o nome do deployment como nome do modelo", + enterModelName: "Digite o nome do modelo", + embeddingChangeTitle: "Alteração de Modelo de Embedding", + embeddingChangeConfirm: "Você está prestes a alterar seu modelo de embedding de {from} para {to}.", + rebuildRequired: "Importante: Reconstrução Necessária", + rebuildReason: "Alterar seu modelo de embedding requer reconstruir todos os embeddings existentes para manter a consistência. Sem reconstruir, suas buscas podem retornar resultados incorretos ou incompletos.", + whatHappensNext: "O que acontece em seguida:", + step1: "Seu modelo de embedding padrão será atualizado", + step2: "Embeddings existentes permanecerão inalterados até a reconstrução", + step3: "Novo conteúdo usará o novo modelo de embedding", + step4: "Você deve reconstruir os embeddings o mais rápido possível", + proceedToRebuildPrompt: "Gostaria de ir para a página Avançado para iniciar a reconstrução agora?", + changeModelOnly: "Apenas Alterar Modelo", + changeAndRebuild: "Alterar e Ir para Reconstrução", + } +} diff --git a/frontend/src/lib/locales/zh-CN/index.ts b/frontend/src/lib/locales/zh-CN/index.ts index 9de605a..03b4a1b 100644 --- a/frontend/src/lib/locales/zh-CN/index.ts +++ b/frontend/src/lib/locales/zh-CN/index.ts @@ -128,6 +128,7 @@ export const zhCN = { comingSoon: "敬请期待", retry: "重试", traditionalChinese: "繁体中文", + portuguese: "葡萄牙语", completed: "已完成", saveSuccess: "保存成功", contextModes: { diff --git a/frontend/src/lib/locales/zh-TW/index.ts b/frontend/src/lib/locales/zh-TW/index.ts index 6893936..a2229b5 100644 --- a/frontend/src/lib/locales/zh-TW/index.ts +++ b/frontend/src/lib/locales/zh-TW/index.ts @@ -128,6 +128,7 @@ export const zhTW = { comingSoon: "敬請期待", retry: "重試", traditionalChinese: "繁體中文", + portuguese: "葡萄牙語", completed: "已完成", saveSuccess: "儲存成功", contextModes: { diff --git a/frontend/src/lib/utils/date-locale.ts b/frontend/src/lib/utils/date-locale.ts index 5ff6243..2d34a3c 100644 --- a/frontend/src/lib/utils/date-locale.ts +++ b/frontend/src/lib/utils/date-locale.ts @@ -1,4 +1,4 @@ -import { zhCN, enUS, zhTW, Locale } from 'date-fns/locale' +import { zhCN, enUS, zhTW, ptBR, Locale } from 'date-fns/locale' /** * Mapping of language codes to date-fns locales. @@ -8,6 +8,7 @@ const LOCALE_MAP: Record = { 'zh-CN': zhCN, 'zh-TW': zhTW, 'en-US': enUS, + 'pt-BR': ptBR, } /**