feat: UX polish, localization, advert relocation, and map filter fixes

- i18n: translate connection screen + chat tab keys into 13 locales;
  add "Send my contact" (advert) strings in all locales
- feat: move self-advert from home header into composer "+" action menu
  (new lib/utils/advert_helper.dart, always shows Flood/Direct sheet)
- fix: hide-repeaters map toggle was bypassed in simple mode
- fix: simple mode map now shows only favourite chat contacts
- fix: wrap ListTiles in Material (contact_tile secondary actions,
  inferred contact group card) to satisfy Flutter ink assertions
- device-authoritative contact/channel sync and UX review fixes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Janez T
2026-06-12 10:01:10 +02:00
parent 3e1113035d
commit ba27843166
62 changed files with 12546 additions and 2521 deletions

View File

@@ -2493,5 +2493,125 @@
"enterRegionName": "Nome da região (ex. auckland)",
"noRegionsFound": "Nenhuma região encontrada neste repetidor.",
"regionScopeSet": "Âmbito de região definido para {name}",
"regionScopeCleared": "Âmbito de região limpo"
"regionScopeCleared": "Âmbito de região limpo",
"simpleMode": "Modo simples",
"simpleModeDescription": "Apenas chat e mapa com contatos favoritos",
"simpleModeRequiresLockedDestination": "Ative primeiro \"Bloquear destino\" com um canal para usar o modo simples",
"noFavouriteContacts": "Nenhum contato favorito",
"selectDestinationFirst": "Selecione primeiro um canal, contato ou sala",
"recipientNoLongerAvailable": "O destinatário não está mais disponível",
"failedToSendMessage": "Falha ao enviar: {error}",
"tictactoeDirectOnly": "O jogo da velha funciona apenas em mensagens diretas. Escolha primeiro um contato.",
"deviceKeyUnavailable": "A chave do dispositivo não está disponível",
"imageCompressionFailed": "Falha na compressão da imagem",
"imageFragmentationFailed": "Falha na fragmentação da imagem",
"failedToAnnounceImage": "Falha ao anunciar a imagem",
"noRecipientSelected": "Nenhum destinatário selecionado",
"imageSendFailed": "Falha no envio da imagem",
"microphonePermissionRequiredForVoice": "É necessária permissão de microfone para mensagens de voz",
"recordingDiscarded": "Gravação descartada",
"publicChannelMediaWarning": "Você está prestes a enviar {mediaType} para o canal público. Isso não é recomendado, pois todos na rede mesh podem receber. Escolha um canal privado ou com tag, a menos que seja isso que você quer.",
"selectPrivateChannelToShareLocation": "Selecione um canal privado para compartilhar sua localização.",
"stopSharingMyLocation": "Parar de compartilhar minha localização",
"shareMyLocation": "Compartilhar minha localização",
"stopRecording": "Parar gravação",
"recordVoice": "Gravar mensagem de voz",
"moreActions": "Mais ações",
"noMessagesInFilter": "Nenhuma mensagem neste filtro",
"noMatchesInFilter": "Nenhum resultado neste filtro",
"syncFailed": "Falha na sincronização: {error}",
"invalidLink": "Link inválido",
"cannotOpenLink": "Não é possível abrir o link",
"failedToOpenLink": "Falha ao abrir o link",
"failedToResendMessage": "Falha ao reenviar a mensagem",
"retryFailed": "Falha na nova tentativa: {error}",
"deleteDrawingAndMessage": "Excluir desenho e mensagem",
"lastEchoRelay": "Último eco: repetidor",
"lastEchoPath": "Último eco: caminho",
"lastEchoBytesReport": "Último eco: relatório de bytes",
"noGpsCoordinatesAvailable": "Nenhuma coordenada GPS disponível",
"notASarMarker": "Não é um marcador SAR",
"failedToSaveTemplate": "Falha ao salvar o modelo: {error}",
"drawingNotFound": "Desenho não encontrado",
"unknownDrawingType": "Tipo de desenho desconhecido",
"customMapMarker": "Marcador de mapa personalizado",
"customMapPointLabel": "Ponto: {point}",
"mapIdLabel": "ID do mapa: {id}",
"cannotFetchVoice": "Não é possível obter a voz",
"senderContactUnknown": "O contato do remetente é desconhecido. Sincronize os contatos primeiro.",
"senderRouteUnknown": "A rota do remetente é desconhecida. Sincronize os contatos/o caminho primeiro.",
"messageTooFar": "A mensagem está longe demais ({hops} saltos, máx. {maxHops}).",
"senderRouteNoPathResponse": "A rota do remetente não respondeu à verificação de caminho. Sincronize os contatos/o caminho e tente novamente.",
"senderRouteNoRawResponse": "A rota do remetente não respondeu no caminho de transporte raw.",
"voiceFetchOverHops": "Obter a voz por {hops} saltos pode demorar um pouco.",
"voiceReceiveCanceled": "Recebimento de voz cancelado",
"fetchingMissingVoiceFragments": "Obtendo fragmentos de voz ausentes",
"receivingVoice": "Recebendo voz",
"cannotFetchImage": "Não é possível obter a imagem",
"imageFetchOverHops": "Obter a imagem por {hops} saltos pode demorar um pouco.",
"imageAlreadyBeingReceived": "A imagem já está sendo recebida",
"loadImage": "Carregar imagem",
"imageFetchFailedToSendRequest": "Falha ao enviar a solicitação da imagem",
"imageUnavailable": "Imagem indisponível no momento",
"imageFetchTimedOut": "Tempo esgotado ao obter a imagem",
"imageReceiveCanceled": "Recebimento da imagem cancelado",
"closeImagePreview": "Fechar visualização da imagem",
"recordingReleaseToSend": "Gravando... solte para enviar",
"sendingVoice": "Enviando voz...",
"sendLongPressToRecordVoice": "Enviar (toque longo para gravar voz)",
"discardRecording": "Descartar gravação",
"hopCount": "{count, plural, =1{1 salto} other{{count} saltos}}",
"channelsSectionDescription": "Canais de difusão para o tráfego mesh próximo",
"roomsSectionDescription": "Espaços compartilhados para coordenação contínua da equipe",
"contactsSectionDescription": "Pessoas e dispositivos ao seu alcance direto",
"noRecentChatters": "Sem participantes recentes",
"public": "Público",
"failedToSendTicTacToeMove": "Falha ao enviar a jogada do jogo da velha",
"youWon": "Você venceu",
"opponentWon": "O adversário venceu",
"gameDraw": "Empate",
"yourTurn": "Sua vez",
"opponentTurn": "Vez do adversário",
"pleaseEnterValidCoordinates": "Insira coordenadas válidas",
"locationNotAvailable": "Localização não disponível",
"customCaveMapPoint": "Ponto de mapa de caverna personalizado",
"mapPoint": "Ponto do mapa",
"fetchingMissingImageFragments": "Obtendo fragmentos ausentes…",
"loadingImage": "Carregando…",
"receivingImage": "Recebendo…",
"tapToLoad": "Toque para carregar",
"connectDevice": "Conectar dispositivo",
"chooseTransportSubtitle": "Escolha o transporte Bluetooth, WiFi ou Serial",
"ble": "BLE",
"network": "Rede",
"serial": "Serial",
"scan": "Escanear",
"pressScanToSearchForDevices": "Pressione escanear para procurar dispositivos próximos",
"failedToConnectToDevice": "Falha ao conectar a {name}",
"unknownDevice": "Dispositivo desconhecido",
"showingCachedResultsTapRefresh": "Exibindo resultados em cache. Toque em atualizar para escanear novamente.",
"scanningLocalNetworkOnPort": "Escaneando a rede local em busca de dispositivos MeshCore WiFi na porta {port}",
"cancelScanAndAddServer": "Cancelar escaneamento e adicionar servidor",
"addServer": "Adicionar servidor",
"scanningProgressIps": "Escaneando... {scanned}/{total} IPs",
"cancelAndEnterManually": "Cancelar e inserir manualmente",
"noRecentOrDiscoveredServers": "Ainda não há servidores recentes ou descobertos",
"recentlyUsed": "Usados recentemente",
"connecting": "Conectando...",
"discoveredOnThisNetwork": "Descobertos nesta rede",
"serverNoLongerAvailable": "O servidor em {host}:{port} não está mais disponível. Escaneie novamente para encontrar servidores ativos.",
"failedToConnectToHost": "Falha ao conectar a {host}:{port}",
"enterValidIpAddress": "Insira um endereço IP válido",
"enterValidTcpPort": "Insira uma porta TCP válida",
"ipAddress": "Endereço IP",
"tcpPort": "Porta TCP",
"customServerPort": "Porta de servidor personalizada",
"chat": "Chat",
"restoringPreviousLink": "Restaurando a conexão anterior",
"noDeviceConnected": "Nenhum dispositivo conectado",
"sendAdvert": "Enviar meu contato",
"advertMode": "Enviar meu contato",
"advertModeSubtitle": "Escolha até onde suas informações de contato devem chegar.",
"floodAdvertSent": "Contato enviado por toda a rede mesh",
"directAdvertSent": "Contato enviado para os dispositivos próximos"
}