feat(i18n): Replace hardcoded strings with translation keys in support and donation components

Replaced hardcoded Portuguese text with next-intl translation keys in the support page, header, and donation card components to enable internationalization and support multiple languages. This improves the app's accessibility and localization capabilities.
This commit is contained in:
Evandro Fonseca Junior committed 2025-10-26 22:40:21 -03:00
1 parent 5ccc9120c4
commit bb6b320d1f
5 files changed
+196 -63

No files matched your search

+6 -6
View File
@@ -168,7 +168,7 @@ export default async function SupportPage({
<BlurFade delay={0.5}>
<div className="mb-20">
<h2 className="text-3xl font-bold text-center mb-8">
Escolha como apoiar
{t("cta.support")}
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 max-w-6xl mx-auto">
<DonationCard
@@ -182,10 +182,10 @@ export default async function SupportPage({
<DonationCard
id="snack"
amount={10}
title="Um Lanche"
description="Equivale a ~20 minutos de desenvolvimento"
title={t("tiers.snack.title")}
description={t("tiers.snack.description")}
icon="pizza"
badge="Popular"
badge={t("tiers.snack.badge")}
highlighted
/>
<DonationCard
@@ -198,8 +198,8 @@ export default async function SupportPage({
/>
<DonationCard
id="custom"
title="Personalizado"
description="Escolha quanto quer doar"
title={t("tiers.custom.title")}
description={t("tiers.custom.description")}
icon="heart"
/>
</div>
+1 -1
View File
@@ -23,7 +23,7 @@ export default function Header() {
{ href: `/${locale}/market`, label: t("market") },
{ href: `/${locale}/sobre`, label: t("about") },
{ href: `/${locale}/precos`, label: t("prices") },
{ href: `/${locale}/apoiar`, label: "Apoiar", highlight: true },
{ href: `/${locale}/apoiar`, label: t("support"), highlight: true },
];
return (
+69 -54
View File
@@ -26,6 +26,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { DonationTier, DonationResponse } from "@/types/donation";
import { useTranslations } from "next-intl";
interface DonationCardProps extends DonationTier {}
@@ -63,6 +64,8 @@ export function DonationCard({
const [paymentConfirmed, setPaymentConfirmed] = useState(false);
const [showThankYouModal, setShowThankYouModal] = useState(false);
const t = useTranslations("donation");
const icons = {
coffee: Coffee,
pizza: Pizza,
@@ -440,27 +443,31 @@ export function DonationCard({
htmlFor="customAmount"
className="text-sm text-muted-foreground"
>
Valor (R$)
{t("form.value")}
</label>
<Input
id="customAmount"
type="text"
inputMode="decimal"
placeholder="Ex: 15,50 ou 15.50"
placeholder={t("form.chooseAmount")}
value={customAmount}
onChange={handleCustomAmountChange}
className="text-lg text-center"
/>
<p className="text-xs text-muted-foreground">
Mín: R$ {customProduct.minAmount.toFixed(2)} | Máx: R${" "}
{customProduct.maxAmount.toFixed(2)}
{t("form.min")}: R$ {customProduct.minAmount.toFixed(2)} |{" "}
{t("form.max")}: R$ {customProduct.maxAmount.toFixed(2)}
</p>
</div>
{customAmount && !isValidCustomAmount() && (
<p className="text-xs text-red-500">
{parseFloat(customAmount) < customProduct.minAmount
? `Mínimo é R$ ${customProduct.minAmount.toFixed(2)}`
: `Máximo é R$ ${customProduct.maxAmount.toFixed(2)}`}
? `${t(
"form.minAmount"
)} ${customProduct.minAmount.toFixed(2)}`
: `${t(
"form.maxAmount"
)} ${customProduct.maxAmount.toFixed(2)}`}
</p>
)}
</div>
@@ -474,12 +481,14 @@ export function DonationCard({
{loading ? (
<>
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
Processando...
{t("form.processing")}
</>
) : customAmount ? (
`Doar R$ ${parseFloat(customAmount || "0").toFixed(2)}`
`${t("form.donate")} ${parseFloat(
customAmount || "0"
).toFixed(2)}`
) : (
"Escolha o valor"
t("form.chooseAmount")
)}
</Button>
</>
@@ -500,10 +509,10 @@ export function DonationCard({
{loading ? (
<>
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
Processando...
{t("form.processing")}
</>
) : (
"Apoiar com PIX"
t("form.supportWithPix")
)}
</Button>
</>
@@ -516,7 +525,7 @@ export function DonationCard({
<DialogContent className="max-w-4xl">
<DialogHeader>
<DialogTitle className="text-center text-2xl">
Como deseja contribuir?
{t("form.howToContribute")}
</DialogTitle>
</DialogHeader>
@@ -530,13 +539,15 @@ export function DonationCard({
<div className="p-2 rounded-full bg-muted">
<Lock className="h-5 w-5" />
</div>
<h3 className="text-xl font-bold">Doação Anônima</h3>
<h3 className="text-xl font-bold">
{t("form.anonymousTitle")}
</h3>
</div>
<p className="text-muted-foreground mb-4">
Rápido e sem cadastro. Gere o PIX imediatamente.
{t("form.anonymousDesc")}
</p>
<Button className="w-full" variant="outline" disabled={loading}>
Continuar Anônimo
{t("form.anonymousButton")}
</Button>
</div>
@@ -546,35 +557,37 @@ export function DonationCard({
onClick={handleIdentifiedClick}
>
<Badge className="absolute -top-3 right-6 bg-primary">
Recomendado
{t("form.recommended")}
</Badge>
<div className="flex items-center gap-3 mb-3">
<div className="p-2 rounded-full bg-primary/10">
<User className="h-5 w-5 text-primary" />
</div>
<h3 className="text-xl font-bold">Quero me Identificar</h3>
<h3 className="text-xl font-bold">
{t("form.identifiedTitle")}
</h3>
</div>
<p className="text-muted-foreground mb-4">
Ajude-nos a agradecer e oferecer benefícios
{t("form.identifiedDesc")}
</p>
<div className="space-y-2 mb-4">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<CheckCircle className="h-4 w-4 text-primary" />
<span>Agradecimento personalizado por email</span>
<span>{t("form.thankByEmail")}</span>
</div>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<CheckCircle className="h-4 w-4 text-primary" />
<span>Acesso antecipado a novos recursos</span>
<span>{t("form.earlyAccess")}</span>
</div>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<CheckCircle className="h-4 w-4 text-primary" />
<span>Histórico de suas contribuições</span>
<span>{t("form.contributionHistory")}</span>
</div>
</div>
<Button className="w-full" disabled={loading}>
Identificar-me
{t("form.identifyMe")}
</Button>
</div>
</div>
@@ -585,7 +598,7 @@ export function DonationCard({
<Dialog open={showDonorForm} onOpenChange={setShowDonorForm}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>Informações para Doação</DialogTitle>
<DialogTitle>{t("form.donorInfo")}</DialogTitle>
</DialogHeader>
<div className="space-y-4">
@@ -597,47 +610,51 @@ export function DonationCard({
<div>
<label className="text-sm font-medium mb-2 block">
Nome *{" "}
{t("form.name")}{" "}
<span className="text-muted-foreground text-xs">
(min. 3 caracteres)
({t("form.nameHint")})
</span>
</label>
<Input
value={donorName}
onChange={(e) => setDonorName(e.target.value)}
placeholder="Seu nome ou apelido"
placeholder={t("form.namePlaceholder")}
maxLength={50}
/>
</div>
<div>
<label className="text-sm font-medium mb-2 block">Email *</label>
<label className="text-sm font-medium mb-2 block">
{t("form.email")}
</label>
<Input
type="email"
value={donorEmail}
onChange={(e) => setDonorEmail(e.target.value)}
placeholder="[email protected]"
placeholder={t("form.emailPlaceholder")}
/>
</div>
<div>
<label className="text-sm font-medium mb-2 block">
Telefone *{" "}
<span className="text-muted-foreground text-xs">(com DDD)</span>
{t("form.phone")}{" "}
<span className="text-muted-foreground text-xs">
({t("form.phoneHint")})
</span>
</label>
<Input
value={donorPhone}
onChange={(e) => setDonorPhone(formatPhone(e.target.value))}
placeholder="(11) 98888-7777"
placeholder={t("form.phonePlaceholder")}
maxLength={15}
/>
</div>
<div>
<label className="text-sm font-medium mb-2 block">
CPF/CNPJ *{" "}
{t("form.taxId")}{" "}
<span className="text-muted-foreground text-xs">
(formato automático)
({t("form.taxIdHint")})
</span>
</label>
<Input
@@ -650,16 +667,16 @@ export function DonationCard({
setDonorTaxId(formatCNPJ(value));
}
}}
placeholder="000.000.000-00 ou 00.000.000/0000-00"
placeholder={t("form.taxIdPlaceholder")}
maxLength={18}
/>
</div>
<div>
<label className="text-sm font-medium mb-2 block">
Mensagem{" "}
{t("form.message")}{" "}
<span className="text-muted-foreground text-xs">
(opcional, máx. 280 chars)
({t("form.messageHint")})
</span>
</label>
<Textarea
@@ -669,7 +686,7 @@ export function DonationCard({
setDonorMessage(e.target.value);
}
}}
placeholder="Deixe uma mensagem para a equipe..."
placeholder={t("form.messagePlaceholder")}
rows={3}
maxLength={280}
/>
@@ -689,7 +706,7 @@ export function DonationCard({
className="flex-1"
disabled={loading}
>
Voltar
{t("form.close")}
</Button>
<Button
onClick={handleSubmitForm}
@@ -699,10 +716,10 @@ export function DonationCard({
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Processando...
{t("form.processing")}
</>
) : (
"Gerar PIX"
t("form.generatePix")
)}
</Button>
</div>
@@ -715,7 +732,7 @@ export function DonationCard({
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle className="text-center">
Escaneie o QR Code para Pagar
{t("form.qrTitle")}
</DialogTitle>
</DialogHeader>
@@ -731,7 +748,7 @@ export function DonationCard({
<div className="space-y-2">
<p className="text-sm text-muted-foreground text-center">
Ou copie o código PIX:
{t("form.copyCode")}
</p>
<div className="flex gap-2">
<Input
@@ -747,12 +764,12 @@ export function DonationCard({
{copied ? (
<>
<Check className="h-4 w-4" />
Copiado!
{t("form.copied")}
</>
) : (
<>
<Copy className="h-4 w-4" />
Copiar
{t("form.copy")}
</>
)}
</Button>
@@ -761,7 +778,7 @@ export function DonationCard({
{timeRemaining && (
<p className="text-xs text-center text-muted-foreground">
⏱️ Tempo restante: {timeRemaining}
⏱️ {t("form.remainingTime")} {timeRemaining}
</p>
)}
@@ -771,13 +788,13 @@ export function DonationCard({
variant="outline"
className="flex-1"
>
Fechar
{t("form.close")}
</Button>
<Button
onClick={() => (window.location.href = "/apoiar/obrigado")}
className="flex-1"
>
Já Paguei
{t("form.paid")}
</Button>
</div>
</div>
@@ -802,19 +819,17 @@ export function DonationCard({
{/* Título e mensagem */}
<div>
<h2 className="text-2xl font-bold mb-2">
Pagamento Confirmado! 🎉
{t("form.paymentConfirmed")} 🎉
</h2>
<p className="text-muted-foreground">
Obrigado pela sua contribuição! Cada doação nos ajuda a manter o
projeto funcionando.
{t("form.paymentConfirmedDesc")}
</p>
</div>
{/* Mensagem especial */}
<div className="p-4 rounded-lg bg-primary/5 border border-primary/20">
<p className="text-sm text-muted-foreground">
Sua doação faz toda a diferença para nossa comunidade. Muito
obrigado pelo apoio! ❤️
{t("form.contributionMatters")} ❤️
</p>
</div>
@@ -828,7 +843,7 @@ export function DonationCard({
className="flex-1"
size="lg"
>
Ver Página de Agradecimento
{t("form.viewThankYouPage")}
</Button>
<Button
onClick={() => {
@@ -839,7 +854,7 @@ export function DonationCard({
className="flex-1"
size="lg"
>
Voltar ao Início
{t("form.backToHome")}
</Button>
</div>
</div>
+60 -1
View File
@@ -3,7 +3,8 @@
"nav": {
"market": "Market",
"about": "About",
"prices": "Prices"
"prices": "Prices",
"support": "Support"
},
"theme": {
"light": "Light Theme",
@@ -225,6 +226,11 @@
"description": "Equals ~10 minutes of development",
"badge": "Popular"
},
"snack": {
"title": "A Snack",
"description": "Equals ~20 minutes of development",
"badge": "Popular"
},
"lunch": {
"title": "Team Lunch",
"description": "Equals ~30 minutes of development",
@@ -241,6 +247,59 @@
"final": "Every contribution matters",
"subtitle": "Each donation helps us dedicate more time to the project and keep the infrastructure running"
},
"form": {
"chooseAmount": "Choose the amount",
"donate": "Donate R$",
"processing": "Processing...",
"supportWithPix": "Support with PIX",
"value": "Amount (R$)",
"min": "Min",
"max": "Max",
"minAmount": "Minimum is R$",
"maxAmount": "Maximum is R$",
"howToContribute": "How would you like to contribute?",
"anonymousTitle": "Anonymous Donation",
"anonymousDesc": "Quick and no registration. Generate PIX immediately.",
"anonymousButton": "Continue Anonymous",
"identifiedTitle": "I Want to Identify Myself",
"identifiedDesc": "Help us thank you and offer benefits",
"recommended": "Recommended",
"thankByEmail": "Personalized thank you by email",
"earlyAccess": "Early access to new features",
"contributionHistory": "History of your contributions",
"identifyMe": "Identify Me",
"donorInfo": "Donation Information",
"name": "Name *",
"nameHint": "min. 3 characters",
"namePlaceholder": "Your name or nickname",
"email": "Email *",
"emailPlaceholder": "[email protected]",
"phone": "Phone *",
"phoneHint": "with area code",
"phonePlaceholder": "(11) 98888-7777",
"taxId": "CPF/CNPJ *",
"taxIdHint": "for receipt issuance",
"taxIdPlaceholder": "000.000.000-00 or 00.000.000/0000-00",
"message": "Message (optional)",
"messageHint": "max. 280 characters",
"messagePlaceholder": "Leave a message for the team...",
"generatePix": "Generate PIX",
"qrTitle": "Scan the QR Code to Pay",
"copyCode": "Or copy the PIX code:",
"copy": "Copy",
"copied": "Copied!",
"expiresIn": "Expires in:",
"expired": "Expired",
"remainingTime": "Remaining time:",
"paid": "I Already Paid",
"close": "Close",
"back": "Back",
"paymentConfirmed": "Payment Confirmed!",
"paymentConfirmedDesc": "Thank you for your contribution! Each donation helps us keep the project running.",
"contributionMatters": "Your donation makes all the difference for our community. Thank you very much for your support!",
"viewThankYouPage": "View Thank You Page",
"backToHome": "Back to Home"
},
"faq": {
"title": "Frequently Asked Questions",
"q1": "Is the project free?",
+60 -1
View File
@@ -3,7 +3,8 @@
"nav": {
"market": "Mercado",
"about": "Sobre",
"prices": "Preços"
"prices": "Preços",
"support": "Apoiar"
},
"theme": {
"light": "Tema Claro",
@@ -225,6 +226,11 @@
"description": "Equivale a ~10 minutos de desenvolvimento",
"badge": "Popular"
},
"snack": {
"title": "Um Lanche",
"description": "Equivale a ~20 minutos de desenvolvimento",
"badge": "Popular"
},
"lunch": {
"title": "Almoço da Equipe",
"description": "Equivale a ~30 minutos de desenvolvimento",
@@ -241,6 +247,59 @@
"final": "Toda contribuição é importante",
"subtitle": "Cada doação nos ajuda a dedicar mais tempo ao projeto e manter a infraestrutura funcionando"
},
"form": {
"chooseAmount": "Escolha o valor",
"donate": "Doar R$",
"processing": "Processando...",
"supportWithPix": "Apoiar com PIX",
"value": "Valor (R$)",
"min": "Mín",
"max": "Máx",
"minAmount": "Mínimo é R$",
"maxAmount": "Máximo é R$",
"howToContribute": "Como deseja contribuir?",
"anonymousTitle": "Doação Anônima",
"anonymousDesc": "Rápido e sem cadastro. Gere o PIX imediatamente.",
"anonymousButton": "Continuar Anônimo",
"identifiedTitle": "Quero me Identificar",
"identifiedDesc": "Ajude-nos a agradecer e oferecer benefícios",
"recommended": "Recomendado",
"thankByEmail": "Agradecimento personalizado por email",
"earlyAccess": "Acesso antecipado a novos recursos",
"contributionHistory": "Histórico de suas contribuições",
"identifyMe": "Identificar-me",
"donorInfo": "Informações para Doação",
"name": "Nome *",
"nameHint": "min. 3 caracteres",
"namePlaceholder": "Seu nome ou apelido",
"email": "Email *",
"emailPlaceholder": "[email protected]",
"phone": "Telefone *",
"phoneHint": "com DDD",
"phonePlaceholder": "(11) 98888-7777",
"taxId": "CPF/CNPJ *",
"taxIdHint": "para emissão de recibo",
"taxIdPlaceholder": "000.000.000-00 ou 00.000.000/0000-00",
"message": "Mensagem (opcional)",
"messageHint": "máx. 280 caracteres",
"messagePlaceholder": "Deixe uma mensagem para a equipe...",
"generatePix": "Gerar PIX",
"qrTitle": "Escaneie o QR Code para Pagar",
"copyCode": "Ou copie o código PIX:",
"copy": "Copiar",
"copied": "Copiado!",
"expiresIn": "Expira em:",
"expired": "Expirado",
"remainingTime": "Tempo restante:",
"paid": "Já Paguei",
"close": "Fechar",
"back": "Voltar",
"paymentConfirmed": "Pagamento Confirmado!",
"paymentConfirmedDesc": "Obrigado pela sua contribuição! Cada doação nos ajuda a manter o projeto funcionando.",
"contributionMatters": "Sua doação faz toda a diferença para nossa comunidade. Muito obrigado pelo apoio!",
"viewThankYouPage": "Ver Página de Agradecimento",
"backToHome": "Voltar ao Início"
},
"faq": {
"title": "Perguntas Frequentes",
"q1": "O projeto é gratuito?",