From f247f046a18ac3e3b6e36ba7b57c4e5baafe9fa8 Mon Sep 17 00:00:00 2001 From: stayzappy Date: Sun, 26 Jul 2026 14:38:21 +0000 Subject: [PATCH 1/2] feat: add Portuguese (pt) locale --- app/src/components/LanguageSwitcher.tsx | 1 + app/src/lib/i18n.tsx | 120 +++++++++++++++++++++++- 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/app/src/components/LanguageSwitcher.tsx b/app/src/components/LanguageSwitcher.tsx index 2867d7d..6326fd5 100644 --- a/app/src/components/LanguageSwitcher.tsx +++ b/app/src/components/LanguageSwitcher.tsx @@ -16,6 +16,7 @@ export default function LanguageSwitcher() { + ); } diff --git a/app/src/lib/i18n.tsx b/app/src/lib/i18n.tsx index 0457c7e..3d56a7f 100644 --- a/app/src/lib/i18n.tsx +++ b/app/src/lib/i18n.tsx @@ -1,6 +1,6 @@ import { createContext, useContext, useState, ReactNode } from "react"; -export type Language = "en" | "vi" | "it" | "tr" | "ru" | "ja"; +export type Language = "en" | "vi" | "it" | "tr" | "ru" | "ja" | "pt"; export const LANGUAGE_STORAGE_KEY = "tributary-lang"; @@ -10,7 +10,7 @@ export function readSavedLanguage(): Language { } const saved = localStorage.getItem(LANGUAGE_STORAGE_KEY); - return saved === "vi" || saved === "it" || saved === "tr" || saved === "ru" || saved === "ja" || saved === "en" ? saved : "en"; + return saved === "vi" || saved === "it" || saved === "tr" || saved === "ru" || saved === "ja" || saved === "pt" || saved === "en" ? saved : "en"; } export function persistLanguage(lang: Language) { @@ -671,6 +671,122 @@ const translations = { // Footer contractOnTestnet: "テストネット上のコントラクト", }, + pt: { + // Header & Navigation + connectWallet: "Conectar Freighter", + github: "GitHub", + testnet: "Testnet", + // Intro + introTitle: "Divida pagamentos na Stellar", + introDesc: "Uma transação, cada destinatário pago pela sua parte. Rodando na testnet.", + // ActionPanel tabs + tabCreate: "Criar", + tabPay: "Pagar", + tabEscrow: "Custódia", + tabManage: "Gerenciar", + // CreateSplit + createTitle: "Criar uma divisão", + createEditableLabel: "Posso editar esta divisão mais tarde (desmarque para travá-la para sempre)", + createButton: "Criar divisão", + waitingForSignature: "Aguardando assinatura…", + connectWalletFirst: "Conecte sua carteira primeiro.", + splitCreated: "Divisão #{id} criada.", + contractRejectedSplit: "Contrato rejeitou a divisão.", + // RecipientEditor + sharesTotalError: "As partes devem somar 100%.", + sharesTotalWarn: "O total deve ser 10.000 bps (100%). Atualmente {total} bps.", + sharesGreaterZeroError: "As partes devem ser maiores que zero.", + recipientRequiredError: "Cada destinatário precisa de um endereço ou id de divisão.", + recipientFormatError: "Endereços devem ser chaves de conta G…", + kindAddress: "Endereço", + kindSplit: "Divisão", + placeholderAddress: "Endereço G… do destinatário", + placeholderSplit: "Id da divisão", + addRecipient: "Adicionar destinatário", + importCsv: "Importar CSV", + pctOfTotal: "{pct}% de 100%", + useBasisPoints: "Usar pontos base", + bpsOfTotal: "{bps} de 10.000 bps", + unitBpsTitle: "Pontos base (100 bps = 1%)", + unitPctTitle: "Porcentagem do total. Armazenado on-chain como pontos base.", + // PaySplit + payTitle: "Pagar através de uma divisão", + chooseSplit: "Escolher divisão", + recipientsCount: "{count} destinatários", + amount: "Quantia", + paySuccess: "Pago {amount} {token} através da divisão #{id}.", + payFailed: "Pagamento falhou.", + payButton: "Pagar", + pickSplitAndAmount: "Escolha uma divisão e uma quantia.", + trustlineWarningTitle: "Não é possível pagar em {token}", + trustlineWarningItem: "{address} não possui trustline de {token}. Eles devem adicioná-la antes que esta divisão possa ser paga em {token}.", + trustlineWarningHint: "O pagamento ficará bloqueado até que todos os destinatários possam receber este token.", + trustlineNoticeTitle: "Verificação de trustline inconclusiva", + trustlineNoticeHint: "Não foi possível verificar trustlines para alguns destinatários. O pagamento pode falhar se não puderem receber este token.", + // EscrowCard + escrowTitle: "Custódia", + escrowDesc: "Deposite fundos em uma divisão agora, pague a todos depois.", + pending: "Pendente: {amount} {token}", + depositButton: "Depositar", + distributeButton: "Distribuir", + distributeSuccess: "Distribuído {amount} {token} para todos os destinatários.", + distributeFailed: "Nada a distribuir.", + depositSuccess: "Depositado {amount} {token}.", + depositFailed: "Depósito falhou.", + pickSplit: "Escolha uma divisão.", + working: "Processando…", + // ManageSplit + manageTitle: "Gerencie suas divisões", + chooseSplitControl: "Escolha uma divisão que você controla", + updateButton: "Atualizar divisão", + placeholderController: "G… novo controlador", + transferButton: "Transferir", + lockButton: "Travar para sempre", + confirmLockButton: "Confirmar trava", + updateSuccess: "Divisão atualizada.", + updateFailed: "Atualização rejeitada.", + transferSuccess: "Controle transferido.", + transferFailed: "Transferência rejeitada.", + lockConfirmPrompt: "Travar é permanente. Pressione novamente para confirmar.", + lockSuccess: "Divisão travada para sempre.", + lockFailed: "Trava rejeitada.", + controllerFormatError: "O controlador deve ser uma chave de conta G….", + // SplitList & Detail + loadingSplits: "Carregando divisões…", + noSplitsOnContract: "Ainda não há divisões neste contrato.", + noSplitsPrompt: "Conecte o Freighter na testnet, abra a aba Criar e registre a primeira. XLM da testnet é gratuito, então não custa nada experimentar.", + recentSplits: "Divisões recentes", + copy: "Copiar", + yours: "sua", + mutable: "mutável", + locked: "travada", + nestedSplit: "divisão #{id}", + detailEscrow: "custódia", + detailController: "controlador: {controller}", + detailHistoryTitle: "Histórico de Pagamentos e Distribuições", + detailHistoryEmpty: "Nenhum pagamento ou distribuição ainda.", + detailHistoryLoading: "Carregando histórico…", + // Activity + recentActivity: "Atividade recente", + exportCsv: "Exportar CSV", + activityCreated: "criada", + activityPaid: "paga", + activityUpdated: "atualizada", + activityDeposit: "depósito", + activityDistributed: "distribuída", + activityControlMoved: "controle transferido", + activityTx: "tx", + activitySplitNum: "divisão #{id}", + // FeeHint + estimatedFee: "Taxa estimada", + estimatedDepositFee: "Taxa de depósito estimada", + estimatedDistributeFee: "Taxa de distribuição estimada", + estimatedUpdateFee: "Taxa de atualização estimada", + estimatedTransferFee: "Taxa de transferência estimada", + estimatedLockFee: "Taxa de trava estimada", + // Footer + contractOnTestnet: "Contrato na testnet", + }, }; interface I18nContextType { From d418a3abca8510735328e153ffcc533b633f90ab Mon Sep 17 00:00:00 2001 From: stayzappy Date: Tue, 28 Jul 2026 11:32:03 +0000 Subject: [PATCH 2/2] fix(ci): remove duplicate imports and describe blocks --- app/src/lib/tributary.test.ts | 40 ----------------------------------- 1 file changed, 40 deletions(-) diff --git a/app/src/lib/tributary.test.ts b/app/src/lib/tributary.test.ts index ec2522d..90d2442 100644 --- a/app/src/lib/tributary.test.ts +++ b/app/src/lib/tributary.test.ts @@ -1,12 +1,3 @@ -import { describe, it, expect } from "vitest"; -import { - toStroops, - fromStroops, - formatAmount, - shortAddress, - recipientLabel, - ConversionError, -} from "./tributary"; import { describe, expect, it } from "vitest"; import { ConversionError, @@ -296,35 +287,4 @@ describe("Token conversion functions", () => { }); }); -describe("formatAmount", () => { - it("formats valid decimal string with commas", () => { - expect(formatAmount("1000")).toBe("1,000"); - }); - - it("returns input string if non-numeric", () => { - expect(formatAmount("abc")).toBe("abc"); - }); -}); - -describe("shortAddress", () => { - it("shortens stellar address", () => { - expect( - shortAddress("GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFXYFTRE65OTHVRWPAHV7") - ).toBe("GBRP…AHV7"); - }); -}); -describe("recipientLabel", () => { - it("formats account recipient", () => { - expect( - recipientLabel({ - tag: "Account", - values: ["GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFXYFTRE65OTHVRWPAHV7"], - }) - ).toBe("GBRP…AHV7"); - }); - - it("formats split recipient", () => { - expect(recipientLabel({ tag: "Split", values: [42n] })).toBe("split #42"); - }); -});