From 2fa30ac45b2eb66ae6f16c4ade0f1f2ca5f93b15 Mon Sep 17 00:00:00 2001 From: joaovictorjs Date: Thu, 19 Mar 2026 20:49:46 -0300 Subject: [PATCH] feat: add pt-BR language support --- src/contexts/I18nContext.tsx | 16 ++++ src/i18n/config.ts | 2 +- src/i18n/locales/pt-BR/common.json | 26 ++++++ src/i18n/locales/pt-BR/dialogs.json | 62 ++++++++++++++ src/i18n/locales/pt-BR/editor.json | 118 ++++++++++++++++++++++++++ src/i18n/locales/pt-BR/launch.json | 62 ++++++++++++++ src/i18n/locales/pt-BR/settings.json | 96 +++++++++++++++++++++ src/i18n/locales/pt-BR/shortcuts.json | 16 ++++ src/i18n/locales/pt-BR/timeline.json | 38 +++++++++ 9 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 src/i18n/locales/pt-BR/common.json create mode 100644 src/i18n/locales/pt-BR/dialogs.json create mode 100644 src/i18n/locales/pt-BR/editor.json create mode 100644 src/i18n/locales/pt-BR/launch.json create mode 100644 src/i18n/locales/pt-BR/settings.json create mode 100644 src/i18n/locales/pt-BR/shortcuts.json create mode 100644 src/i18n/locales/pt-BR/timeline.json diff --git a/src/contexts/I18nContext.tsx b/src/contexts/I18nContext.tsx index c62e818..8fbdaba 100644 --- a/src/contexts/I18nContext.tsx +++ b/src/contexts/I18nContext.tsx @@ -35,6 +35,13 @@ import zhCNLaunch from '@/i18n/locales/zh-CN/launch.json' import zhCNSettings from '@/i18n/locales/zh-CN/settings.json' import zhCNShortcuts from '@/i18n/locales/zh-CN/shortcuts.json' import zhCNTimeline from '@/i18n/locales/zh-CN/timeline.json' +import ptBRCommon from '@/i18n/locales/pt-BR/common.json' +import ptBRDialogs from '@/i18n/locales/pt-BR/dialogs.json' +import ptBREditor from '@/i18n/locales/pt-BR/editor.json' +import ptBRLaunch from '@/i18n/locales/pt-BR/launch.json' +import ptBRSettings from '@/i18n/locales/pt-BR/settings.json' +import ptBRShortcuts from '@/i18n/locales/pt-BR/shortcuts.json' +import ptBRTimeline from '@/i18n/locales/pt-BR/timeline.json' const LOCALE_STORAGE_KEY = 'recordly.locale' @@ -68,6 +75,15 @@ const messages: Record = { dialogs: zhCNDialogs, shortcuts: zhCNShortcuts, }, + 'pt-BR': { + common: ptBRCommon, + launch: ptBRLaunch, + editor: ptBREditor, + timeline: ptBRTimeline, + settings: ptBRSettings, + dialogs: ptBRDialogs, + shortcuts: ptBRShortcuts, + }, } as const interface I18nContextValue { diff --git a/src/i18n/config.ts b/src/i18n/config.ts index ce23fd4..df5456c 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -1,6 +1,6 @@ export const DEFAULT_LOCALE = 'en' as const -export const SUPPORTED_LOCALES = ['en', 'es', 'zh-CN'] as const +export const SUPPORTED_LOCALES = ['en', 'es', 'zh-CN', 'pt-BR'] as const export const I18N_NAMESPACES = [ 'common', diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json new file mode 100644 index 0000000..69c846f --- /dev/null +++ b/src/i18n/locales/pt-BR/common.json @@ -0,0 +1,26 @@ +{ + "app": { + "name": "Recordly", + "editorTitle": "Editor Recordly", + "subtitle": "Gravação de tela e edição", + "language": "Idioma", + "manageRecordings": "Abrir pasta de gravações" + }, + "actions": { + "cancel": "Cancelar", + "close": "Fechar", + "export": "Exportar", + "load": "Carregar", + "redo": "Refazer", + "reset": "Redefinir", + "save": "Salvar", + "undo": "Desfazer", + "delete": "Excluir", + "done": "Concluído" + }, + "errors": { + "invalidFileType": "Tipo de arquivo inválido", + "failedToUploadImage": "Falha ao carregar imagem", + "fileReadError": "Ocorreu um erro ao ler o arquivo." + } +} diff --git a/src/i18n/locales/pt-BR/dialogs.json b/src/i18n/locales/pt-BR/dialogs.json new file mode 100644 index 0000000..b201e71 --- /dev/null +++ b/src/i18n/locales/pt-BR/dialogs.json @@ -0,0 +1,62 @@ +{ + "export": { + "pleaseTryAgain": "Por favor, tente novamente", + "compilingGifProgress": "Compilando GIF... {{progress}}%", + "compilingGifWait": "Compilando GIF... Isso pode levar um tempo", + "takeMoment": "Isso pode levar um momento...", + "exportFailed": "Exportação Falhou", + "compilingGifTitle": "Compilando GIF", + "exportingFormat": "Exportando {{format}}", + "exportComplete": "Exportação Concluída", + "formatReady": "Seu {{format}} está pronto", + "showInFolder": "Mostrar na pasta", + "compiling": "Compilando", + "renderingFrames": "Renderizando quadros", + "processing": "Processando...", + "status": "Status", + "format": "Formato", + "compilingStatus": "Compilando...", + "frames": "Quadros", + "cancelExport": "Cancelar exportação", + "reopenSaveDialog": "Abrir diálogo de salvamento novamente", + "savedSuccess": "{{format}} salvo com sucesso!" + }, + "addFont": { + "title": "Adicionar Fonte do Google", + "heading": "Adicionar Fonte do Google", + "description": "Adicione uma fonte personalizada do Google Fonts para usar em suas anotações.", + "urlLabel": "URL de importação do Google Fonts", + "urlPlaceholder": "https://fonts.googleapis.com/css2?family=Roboto&display=swap", + "urlHelp": "Obtenha isso no Google Fonts: Selecione uma fonte → Clique em \"Obter fonte\" → Copie a URL @import", + "nameLabel": "Nome de exibição", + "namePlaceholder": "Minha fonte personalizada", + "nameHelp": "É assim que a fonte aparecerá no seletor de fontes", + "adding": "Adicionando...", + "addFont": "Adicionar fonte", + "enterUrl": "Por favor, insira uma URL de importação do Google Fonts", + "invalidUrl": "Por favor, insira uma URL válida do Google Fonts", + "enterName": "Por favor, insira um nome de fonte", + "extractFailed": "Não foi possível extrair a família de fontes da URL", + "addSuccess": "Fonte \"{{name}}\" adicionada com sucesso", + "addFailed": "Falha ao adicionar fonte", + "loadTimeout": "A fonte demorou demais para carregar. Por favor, verifique a URL e tente novamente.", + "loadFailed": "A fonte não pôde ser carregada. Por favor, verifique se a URL do Google Fonts está correta." + }, + "shortcutsConfig": { + "title": "Atalhos de Teclado", + "configurable": "Configurável", + "fixed": "Fixo", + "pressEscToCancel": "Pressione Esc para cancelar", + "clickToChange": "Clique para alterar", + "pressAKey": "Pressione uma tecla…", + "alreadyUsedBy": "Já usado por {{action}}", + "swap": "Trocar", + "reserved": "Este atalho está reservado para \"{{label}}\" e não pode ser reatribuído.", + "saved": "Atalhos de teclado salvos", + "resetNotice": "Redefinir para atalhos padrão — clique em Salvar para aplicar", + "instructions": "Clique em um atalho e pressione a nova combinação de teclas. Pressione Esc para cancelar.", + "resetToDefaults": "Redefinir para padrão", + "cancel": "Cancelar", + "save": "Salvar" + } +} diff --git a/src/i18n/locales/pt-BR/editor.json b/src/i18n/locales/pt-BR/editor.json new file mode 100644 index 0000000..4413bcd --- /dev/null +++ b/src/i18n/locales/pt-BR/editor.json @@ -0,0 +1,118 @@ +{ + "playback": { + "play": "Reproduzir", + "pause": "Pausar" + }, + "annotations": { + "settings": "Configurações de anotações", + "active": "Ativo", + "text": "Texto", + "image": "Imagem", + "arrow": "Seta", + "textContent": "Conteúdo do texto", + "textPlaceholder": "Digite seu texto...", + "fontStyle": "Estilo da fonte", + "selectStyle": "Selecionar estilo", + "size": "Tamanho", + "toggleBold": "Alternar negrito", + "toggleItalic": "Alternar itálico", + "toggleUnderline": "Alternar sublinhado", + "alignLeft": "Alinhar à esquerda", + "alignCenter": "Alinhar ao centro", + "alignRight": "Alinhar à direita", + "textColor": "Cor do texto", + "background": "Fundo", + "none": "Nenhum", + "clearBackground": "Limpar fundo", + "uploadImage": "Carregar imagem", + "supportedFormats": "Formatos suportados: JPG, PNG, GIF, WebP", + "arrowDirection": "Direção da seta", + "strokeWidth": "Largura do traço: {{width}}px", + "arrowColor": "Cor da seta", + "deleteAnnotation": "Excluir anotação", + "shortcutsAndTips": "Atalhos e dicas", + "tipSelectAnnotation": "Mova o cursor de reprodução para a seção de anotação sobreposta e selecione um item.", + "tipCycleForward": "Use Tab para navegar pelos itens sobrepostos.", + "tipCycleBackward": "Use Shift+Tab para navegar para trás.", + "imageUploadSuccess": "Imagem carregada com sucesso!", + "imageUploadError": "Por favor, carregue um arquivo de imagem JPG, PNG, GIF ou WebP." + }, + "fontStyles": { + "classic": "Clássico", + "editor": "Editor", + "strong": "Forte", + "typewriter": "Máquina de escrever", + "deco": "Deco", + "simple": "Simples", + "modern": "Moderno", + "clean": "Limpo" + }, + "format": { + "mp4Video": "Vídeo MP4", + "mp4Description": "Arquivo de vídeo de alta qualidade", + "gifAnimation": "Animação GIF", + "gifDescription": "Imagem animada para compartilhar" + }, + "gifOptions": { + "frameRate": "Taxa de quadros", + "outputSize": "Tamanho de saída", + "outputDimensions": "Saída: {{width}} × {{height}}px", + "loopAnimation": "Animação em loop", + "loopDescription": "O GIF será reproduzido continuamente" + }, + "tutorial": { + "howTrimmingWorks": "Como funciona o corte", + "title": "Como Funciona o Corte", + "understanding": "Entenda como remover as partes indesejadas do seu vídeo.", + "descriptionP1": "A ferramenta de corte funciona definindo os segmentos que você deseja", + "descriptionRemove": "remover", + "descriptionP2": "do seu vídeo.", + "descriptionP3": "Qualquer parte da linha do tempo coberta por um segmento de corte vermelho será removida ao exportar.", + "visualExample": "Exemplo visual", + "removed": "REMOVIDO", + "kept": "Mantido", + "finalVideo": "Vídeo final", + "part": "Parte {{number}}", + "addTrimStep": "1. Adicionar corte", + "addTrimDesc": "Pressione T ou clique no ícone de tesoura para marcar uma seção para remoção.", + "adjustStep": "2. Ajustar", + "adjustDesc": "Arraste as bordas da região vermelha para cobrir exatamente o que você deseja remover." + }, + "feedback": { + "trigger": "Feedback", + "title": "Feedback e contato", + "description": "Entre em contato diretamente ou abra uma issue se algo estiver quebrado ou faltando.", + "emailLabel": "E-mail", + "xLabel": "X", + "reportIssue": "Reportar problema / enviar feedback", + "openFailed": "Falha ao abrir link." + }, + "keyboardShortcuts": { + "trigger": "Atalhos", + "title": "Atalhos de Teclado", + "description": "Referência rápida para os controles da linha do tempo e do editor.", + "customizeTooltip": "Personalizar atalhos", + "customize": "Personalizar", + "panTimeline": "Mover linha do tempo", + "zoomTimeline": "Zoom da linha do tempo", + "cycleAnnotations": "Navegar pelas anotações", + "tab": "Tab" + }, + "actions": { + "saveAgain": "Salvar novamente", + "showInFolder": "Mostrar na pasta" + }, + "project": { + "untitled": "Sem título" + }, + "exportStatus": { + "exporting": "Exportando", + "renderingFile": "Renderizando seu arquivo.", + "preparing": "Preparando exportação...", + "completePercent": "{{percent}}% concluído", + "issue": "Problema na exportação", + "complete": "Exportação concluída", + "savedSuccessfully": "Seu arquivo foi salvo com sucesso." + }, + "openRecordingsFolder": "Abrir pasta de gravações" +} diff --git a/src/i18n/locales/pt-BR/launch.json b/src/i18n/locales/pt-BR/launch.json new file mode 100644 index 0000000..984bb0a --- /dev/null +++ b/src/i18n/locales/pt-BR/launch.json @@ -0,0 +1,62 @@ +{ + "recording": { + "disableSystemAudio": "Desativar áudio do sistema", + "enableSystemAudio": "Ativar áudio do sistema", + "disableMicrophone": "Desativar microfone", + "enableMicrophone": "Ativar microfone", + "disableWebcam": "Desativar sobreposição de câmera", + "enableWebcam": "Ativar sobreposição de câmera", + "countdownDelay": "Atraso da contagem regressiva", + "noDelay": "Sem atraso", + "record": "Gravar", + "recordingFolder": "Pasta de gravações: {{path}}", + "chooseRecordingsFolder": "Escolher pasta de gravações", + "folderPath": "Caminho: /{{name}}/", + "openVideoFile": "Abrir arquivo de vídeo", + "openProject": "Abrir projeto", + "hideHudFromVideo": "Ocultar HUD da gravação", + "showHudInVideo": "Mostrar HUD na gravação", + "hideHud": "Ocultar HUD", + "closeApp": "Fechar aplicativo", + "screens": "Telas", + "windows": "Janelas", + "noSourcesFound": "Nenhuma fonte encontrada", + "microphone": "Microfone", + "turnOffMicrophone": "Desativar microfone", + "selectMicToEnable": "Selecione um microfone para ativar", + "noMicrophonesFound": "Nenhum microfone encontrado", + "webcam": "Câmera", + "turnOffWebcam": "Desativar câmera", + "selectWebcamToEnable": "Selecione uma câmera para ativar", + "noWebcamsFound": "Nenhuma câmera encontrada", + "recordingsFolder": "Pasta de gravações", + "language": "Idioma", + "paused": "PAUSADO", + "rec": "GRAV", + "resume": "Retomar", + "pause": "Pausar", + "stop": "Parar", + "cancel": "Cancelar", + "more": "Mais" + }, + "sourceSelector": { + "loadingSources": "Carregando fontes...", + "screens": "Telas", + "windows": "Janelas", + "windowsNote": "Apenas janelas visíveis (não minimizadas) podem ser gravadas.", + "windowPlaceholder": "Janela", + "cancel": "Cancelar", + "share": "Compartilhar" + }, + "permissions": { + "screenRecordingNeeded": "O Recordly precisa da permissão de gravação de tela antes de começar. As Configurações do sistema foram abertas. Após ativar, feche e reabra o Recordly.", + "screenRecordingMissing": "A permissão de gravação de tela ainda está faltando. As Configurações do sistema foram abertas novamente. Ative, depois feche e reabra o Recordly antes de gravar.", + "accessibilityNeeded": "O Recordly também precisa da permissão de acessibilidade para rastreamento do cursor. As Configurações do sistema foram abertas. Após ativar, feche e reabra o Recordly.", + "accessibilityMissing": "A permissão de acessibilidade ainda está faltando. As Configurações do sistema foram abertas novamente. Ative, depois feche e reabra o Recordly antes de gravar.", + "selectSource": "Por favor, selecione uma fonte para gravar", + "systemAudioUnavailable": "O áudio do sistema não está disponível para esta fonte. A gravação continuará sem áudio do sistema.", + "microphoneDenied": "O acesso ao microfone foi negado. A gravação continuará sem áudio do microfone.", + "failedToStart": "Falha ao iniciar a gravação: {{error}}", + "failedToStartGeneric": "Falha ao iniciar a gravação" + } +} diff --git a/src/i18n/locales/pt-BR/settings.json b/src/i18n/locales/pt-BR/settings.json new file mode 100644 index 0000000..344fdac --- /dev/null +++ b/src/i18n/locales/pt-BR/settings.json @@ -0,0 +1,96 @@ +{ + "zoom": { + "level": "Nível de Zoom", + "selectRegion": "Selecione uma região de zoom para ajustar", + "deleteZoom": "Excluir Zoom" + }, + "trim": { + "deleteRegion": "Excluir Região de Corte" + }, + "speed": { + "playbackSpeed": "Velocidade de Reprodução", + "selectRegion": "Selecione uma região de velocidade para ajustar", + "deleteRegion": "Excluir Região de Velocidade" + }, + "effects": { + "title": "Efeitos de Vídeo", + "show": "Mostrar", + "showCursor": "Mostrar Cursor", + "loopCursor": "Loop do cursor", + "backgroundBlur": "Desfoque de fundo", + "zoomMotionBlur": "Desfoque de movimento do zoom", + "connectZooms": "Conectar Zooms", + "cursorSize": "Tamanho do cursor", + "cursorSmoothing": "Suavização do cursor", + "off": "Desligado", + "cursorMotionBlur": "Desfoque de movimento do cursor", + "cursorClickBounce": "Salto do clique do cursor", + "cursorClickBounceDuration": "Velocidade do salto", + "cursorSway": "Oscilação do cursor", + "webcam": "Sobreposição de webcam", + "webcamFootage": "Gravação de webcam", + "webcamFootageDescription": "Nenhuma gravação de webcam vinculada a este vídeo", + "uploadWebcamFootage": "Carregar gravação", + "replaceWebcamFootage": "Substituir gravação", + "removeWebcamFootage": "Remover gravação", + "webcamFootageAdded": "Gravação de webcam vinculada", + "webcamFootageRemoved": "Gravação de webcam removida", + "webcamSize": "Tamanho da webcam", + "webcamReactToZoom": "Webcam reage ao zoom", + "webcamRoundness": "Arredondamento da webcam", + "webcamShadow": "Sombra da webcam", + "shadow": "Sombra", + "radius": "Raio", + "roundness": "Arredondamento", + "padding": "Preenchimento", + "removeBackground": "Remover fundo" + }, + "sections": { + "scene": "Cena", + "cursor": "Cursor", + "webcam": "Webcam", + "frame": "Quadro", + "crop": "Corte" + }, + "crop": { + "title": "Cortar Vídeo", + "instruction": "Arraste em cada lado para ajustar a área de corte", + "top": "Topo", + "bottom": "Base", + "left": "Esquerda", + "right": "Direita", + "openEditor": "Abrir Editor de Corte" + }, + "background": { + "title": "Fundo", + "image": "Imagem", + "color": "Cor", + "gradient": "Gradiente", + "wallpaperPreview": "Visualização do papel de parede", + "uploadCustom": "Carregar personalizado", + "uploadSuccess": "Imagem personalizada carregada com sucesso!", + "uploadError": "Por favor, carregue um arquivo de imagem JPG ou JPEG.", + "uploadErrorDescription": "Apenas imagens JPG e JPEG são suportadas." + }, + "export": { + "title": "Exportar", + "mp4": "MP4", + "gif": "GIF", + "quality": { + "low": "Baixa", + "medium": "Média", + "high": "Alta", + "original": "Original" + }, + "loop": "Loop", + "outputDimensions": "Saída: {{dimensions}}px", + "sizePresetOriginalShort": "Orig", + "sizePresetMediumShort": "Méd", + "sizePresetLargeShort": "Grand", + "loadProject": "Carregar Projeto", + "saveProject": "Salvar Projeto", + "exportVideo": "Exportar {{format}}", + "reportBug": "Reportar Bug", + "starOnGithub": "Star no GitHub" + } +} diff --git a/src/i18n/locales/pt-BR/shortcuts.json b/src/i18n/locales/pt-BR/shortcuts.json new file mode 100644 index 0000000..d2fb730 --- /dev/null +++ b/src/i18n/locales/pt-BR/shortcuts.json @@ -0,0 +1,16 @@ +{ + "actions": { + "addZoom": "Adicionar Zoom", + "addTrim": "Adicionar Corte", + "addSpeed": "Adicionar Velocidade", + "addAnnotation": "Adicionar Anotação", + "addKeyframe": "Adicionar Keyframe", + "deleteSelected": "Excluir Selecionado", + "playPause": "Reproduzir / Pausar", + "cycleForward": "Navegar Anotações para Frente", + "cycleBackward": "Navegar Anotações para Trás", + "deleteSelectedAlt": "Excluir Selecionado (alt)", + "panTimeline": "Mover Linha do Tempo", + "zoomTimeline": "Zoom da Linha do Tempo" + } +} diff --git a/src/i18n/locales/pt-BR/timeline.json b/src/i18n/locales/pt-BR/timeline.json new file mode 100644 index 0000000..8c35709 --- /dev/null +++ b/src/i18n/locales/pt-BR/timeline.json @@ -0,0 +1,38 @@ +{ + "zoom": { + "cannotPlace": "Não é possível colocar zoom aqui", + "existsOrNoSpace": "Zoom já existe neste local ou espaço insuficiente disponível.", + "suggestHandlerUnavailable": "Manipulador de sugestões de zoom indisponível", + "noTelemetry": "Sem telemetria de cursor disponível", + "recordFirst": "Grave uma screencast primeiro para gerar sugestões baseadas no cursor.", + "noUsableTelemetry": "Sem telemetria de cursor utilizável", + "notEnoughMovement": "A gravação não inclui dados suficientes de movimento do cursor.", + "noInteractionMoments": "Nenhum momento claro de interação encontrado", + "tryRecording": "Tente uma gravação com pausas ou cliques em ações importantes.", + "noAutoZoomSlots": "Nenhum slot de zoom automático disponível", + "dwellPointsOverlap": "Pontos de pausa detectados sobrepõem regiões de zoom existentes.", + "addedSuggestions": "{{count}} sugestão(ões) de zoom baseada(s) em interação adicionada(s)", + "label": "Zoom {{index}}", + "addZoom": "Adicionar Zoom (Z)", + "suggestZooms": "Sugerir Zooms a partir do Cursor" + }, + "trim": { + "cannotPlace": "Não é possível colocar corte aqui", + "existsOrNoSpace": "Corte já existe neste local ou espaço insuficiente disponível.", + "label": "Corte {{index}}", + "addTrim": "Adicionar Corte (T)" + }, + "speed": { + "cannotPlace": "Não é possível colocar velocidade aqui", + "existsOrNoSpace": "Região de velocidade já existe neste local ou espaço insuficiente disponível.", + "label": "Velocidade" + }, + "annotation": { + "label": "Anotação", + "image": "Imagem", + "addAnnotation": "Adicionar Anotação (A)" + }, + "addSpeed": "Adicionar Velocidade (S)", + "resizeLeft": "Redimensionar à esquerda", + "resizeRight": "Redimensionar à direita" +}