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,
}
/**