mirror of
https://github.com/lukevella/rallly.git
synced 2025-06-18 18:41:51 +02:00
Add pt-br locale (#261)
This commit is contained in:
parent
0b156fabe6
commit
1ef15682ea
9 changed files with 226 additions and 18 deletions
|
@ -3,7 +3,7 @@ const path = require("path");
|
||||||
module.exports = {
|
module.exports = {
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: "en",
|
defaultLocale: "en",
|
||||||
locales: ["en", "es", "de", "fr", "sv"],
|
locales: ["en", "es", "de", "fr", "sv", "pt-BR"],
|
||||||
localePath: path.resolve("./public/locales"),
|
localePath: path.resolve("./public/locales"),
|
||||||
reloadOnPrerender: process.env.NODE_ENV === "development",
|
reloadOnPrerender: process.env.NODE_ENV === "development",
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"italian": "Italian",
|
"italian": "Italian",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"links": "Links",
|
"links": "Links",
|
||||||
|
"portugueseBr": "Portuguese (Brazil)",
|
||||||
"poweredBy": "Powered by",
|
"poweredBy": "Powered by",
|
||||||
"privacyPolicy": "Privacy Policy",
|
"privacyPolicy": "Privacy Policy",
|
||||||
"spanish": "Spanish",
|
"spanish": "Spanish",
|
||||||
|
|
128
public/locales/pt-BR/app.json
Normal file
128
public/locales/pt-BR/app.json
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
{
|
||||||
|
"12h": "12-horas",
|
||||||
|
"24h": "24-horas",
|
||||||
|
"addParticipant": "Adicionar participante",
|
||||||
|
"addTimeOption": "Adicionar opção de horário",
|
||||||
|
"alreadyVoted": "Você já votou",
|
||||||
|
"applyToAllDates": "Aplicar a todas as datas",
|
||||||
|
"areYouSure": "Você tem certeza?",
|
||||||
|
"back": "Voltar",
|
||||||
|
"calendarHelp": "Você não pode criar uma enquete sem quaisquer opções. Adicione pelo menos uma opção para continuar.",
|
||||||
|
"calendarHelpTitle": "Esqueceu algo?",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"comment": "Comentar",
|
||||||
|
"commentPlaceholder": "Deixe um comentário nesta enquete (visível para todos)",
|
||||||
|
"comments": "Comentários",
|
||||||
|
"continue": "Continuar",
|
||||||
|
"copied": "Copiado",
|
||||||
|
"copyLink": "Copiar link",
|
||||||
|
"createdBy": "por <b>{{name}}</b>",
|
||||||
|
"createPoll": "Criar enquete",
|
||||||
|
"creatingDemo": "Criando enquete de demonstração…",
|
||||||
|
"delete": "Excluir",
|
||||||
|
"deleteComment": "Apagar comentário",
|
||||||
|
"deleteDate": "Excluir data",
|
||||||
|
"deletedPoll": "Enquete excluída",
|
||||||
|
"deletedPollInfo": "Esta enquete não existe mais.",
|
||||||
|
"deletePoll": "Excluir enquete",
|
||||||
|
"deletePollDescription": "Todos os dados relacionados a esta enquete serão excluídos. Para confirmar, digite <s>“{{confirmText}}”</s> no espaço abaixo:",
|
||||||
|
"deletingOptionsWarning": "Você está excluindo opções que outros participantes já votaram. Esses votos serão excluídos também.",
|
||||||
|
"demoPollNotice": "Enquetes de demonstração são excluídas automaticamente após 1 dia",
|
||||||
|
"description": "Descrição",
|
||||||
|
"descriptionPlaceholder": "Olá a todos! Por gentileza, escolha as datas que sirva para você!",
|
||||||
|
"donate": "Doe",
|
||||||
|
"editDetails": "Editar detalhes",
|
||||||
|
"editOptions": "Editar opções",
|
||||||
|
"email": "E-mail",
|
||||||
|
"emailPlaceholder": "fulano@email.com.br",
|
||||||
|
"endingGuestSessionNotice": "Uma vez que uma sessão de convidado termine, ela não poderá ser retomada. Você não poderá editar nenhum voto ou comentário que tenha feito nesta sessão.",
|
||||||
|
"endSession": "Encerrar sessão",
|
||||||
|
"errorCreate": "Ops! Houve um problema ao criar sua enquete. O erro foi registrado e tentaremos corrigi-lo.",
|
||||||
|
"exportToCsv": "Exportar para CSV",
|
||||||
|
"finish": "Concluir",
|
||||||
|
"forgetMe": "Esqueça-me",
|
||||||
|
"goToAdmin": "Ir para Admin",
|
||||||
|
"guest": "Convidado",
|
||||||
|
"guestSessionNotice": "Você está usando uma sessão de convidado. Isso nos permite reconhecê-lo caso você voltar mais tarde e poder editar seus votos.",
|
||||||
|
"guestSessionReadMore": "Leia mais sobre sessões de convidado.",
|
||||||
|
"hide": "Ocultar",
|
||||||
|
"ifNeedBe": "Se for necessário",
|
||||||
|
"linkHasExpired": "Seu link expirou ou não é mais válido",
|
||||||
|
"loading": "Carregando…",
|
||||||
|
"loadingParticipants": "Carregando participantes…",
|
||||||
|
"location": "Local",
|
||||||
|
"locationPlaceholder": "Loja de Café do Júlio",
|
||||||
|
"lockPoll": "Bloquear enquete",
|
||||||
|
"login": "Logar",
|
||||||
|
"loginCheckInbox": "Por gentileza, verifique sua caixa de entrada.",
|
||||||
|
"loginMagicLinkSent": "Um link mágico foi enviado para:",
|
||||||
|
"loginSendMagicLink": "Envie-me um link mágico",
|
||||||
|
"loginViaMagicLink": "Login via link mágico",
|
||||||
|
"loginViaMagicLinkDescription": "Enviaremos um e-mail com um link mágico que você possa usar para logar.",
|
||||||
|
"loginWithValidEmail": "Por favor, insira um endereço de e-mail válido",
|
||||||
|
"logout": "Deslogar",
|
||||||
|
"manage": "Gerenciar",
|
||||||
|
"menu": "Menu",
|
||||||
|
"mixedOptionsDescription": "Você não pode ter ambas as opções de data e hora na mesma enquete. Qual você gostaria de manter?",
|
||||||
|
"mixedOptionsKeepDates": "Manter opções de data",
|
||||||
|
"mixedOptionsKeepTimes": "Manter opções de hora",
|
||||||
|
"mixedOptionsTitle": "Aguarde um minuto…🤔",
|
||||||
|
"monday": "Segunda-feira",
|
||||||
|
"monthView": "Visão mensal",
|
||||||
|
"name": "Nome",
|
||||||
|
"namePlaceholder": "Fulano de Tal",
|
||||||
|
"newPoll": "Nova enquete",
|
||||||
|
"next": "Avançar",
|
||||||
|
"nextMonth": "Próximo mês",
|
||||||
|
"no": "Não",
|
||||||
|
"noDatesSelected": "Nenhuma data selecionada",
|
||||||
|
"notificationsDisabled": "As notificações foram desabilitadas",
|
||||||
|
"notificationsOff": "Notificações desativadas",
|
||||||
|
"notificationsOn": "Notificações ativadas",
|
||||||
|
"notificationsOnDescription": "Um e-mail será enviado para <b>{{email}}</b> quando houver atividade nesta enquete.",
|
||||||
|
"notificationsVerifyEmail": "Você precisa confirmar seu e-mail para ativar as notificações",
|
||||||
|
"ok": "Ok",
|
||||||
|
"options": "Opções",
|
||||||
|
"participant": "Participante",
|
||||||
|
"participantCount_other": "{{count}} participantes",
|
||||||
|
"participantCount": "{{count}} participante",
|
||||||
|
"pollHasBeenLocked": "Esta enquete foi bloqueada",
|
||||||
|
"pollHasBeenVerified": "Sua enquete foi verificada",
|
||||||
|
"pollOwnerNotice": "Oi {{name}}, parece que você é o proprietário desta enquete.",
|
||||||
|
"pollsEmpty": "Nenhuma enquete criada",
|
||||||
|
"possibleAnswers": "Possíveis respostas",
|
||||||
|
"preferences": "Preferências",
|
||||||
|
"previousMonth": "Mês anterior",
|
||||||
|
"profileLogin": "Perfil - Login",
|
||||||
|
"profileUser": "Perfil - {{username}}",
|
||||||
|
"requiredNameError": "Nome é obrigatório",
|
||||||
|
"save": "Salvar",
|
||||||
|
"saveInstruction": "Selecione sua disponibilidade e clique <b>{{save}}</b>",
|
||||||
|
"share": "Compartilhar",
|
||||||
|
"shareDescription": "Dê este link para os seus <b>participantes</b> para permitir que eles votem na sua enquete.",
|
||||||
|
"shareLink": "Compartilhar via link",
|
||||||
|
"specifyTimes": "Especificar horários",
|
||||||
|
"specifyTimesDescription": "Incluir os horários de início e fim para cada opção",
|
||||||
|
"stepSummary": "Passo {{current}} de {{total}}",
|
||||||
|
"sunday": "Domingo",
|
||||||
|
"timeAndDate": "Hora & data",
|
||||||
|
"timeFormat": "Formato de hora:",
|
||||||
|
"timeZone": "Fuso horário:",
|
||||||
|
"title": "Título",
|
||||||
|
"titlePlaceholder": "Reunião mensal",
|
||||||
|
"today": "Hoje",
|
||||||
|
"unlockPoll": "Desbloquear enquete",
|
||||||
|
"unverifiedMessage": "Um e-mail foi enviado para <b>{{email}}</b> com um link para verificar o endereço de e-mail.",
|
||||||
|
"user": "Usuário",
|
||||||
|
"vote": "Votar",
|
||||||
|
"voteCount_other": "{{count}} votos",
|
||||||
|
"voteCount": "{{count}} voto",
|
||||||
|
"weekStartsOn": "A semana começa em",
|
||||||
|
"weekView": "Visão semanal",
|
||||||
|
"whatsThis": "O que é isso?",
|
||||||
|
"yes": "Sim",
|
||||||
|
"you": "Você",
|
||||||
|
"yourDetails": "Seus detalhes",
|
||||||
|
"yourName": "Seu nome…",
|
||||||
|
"yourPolls": "Suas enquetes"
|
||||||
|
}
|
21
public/locales/pt-BR/common.json
Normal file
21
public/locales/pt-BR/common.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"blog": "Blog",
|
||||||
|
"discussions": "Discussões",
|
||||||
|
"donate": "Doe",
|
||||||
|
"english": "English",
|
||||||
|
"footerCredit": "Feito por <a>@imlukevella</a>",
|
||||||
|
"footerSponsor": "Este projeto é financiado pelo usuário. Por gentileza, considere apoiá-lo fazendo uma <a>doação</a>.",
|
||||||
|
"french": "Français",
|
||||||
|
"german": "Deutsch",
|
||||||
|
"home": "Início",
|
||||||
|
"italian": "L'italiano",
|
||||||
|
"language": "Idioma",
|
||||||
|
"links": "Links",
|
||||||
|
"poweredBy": "Desenvolvido por",
|
||||||
|
"privacyPolicy": "Política de Privacidade",
|
||||||
|
"spanish": "Español",
|
||||||
|
"starOnGithub": "Qualifique-nos no GitHub",
|
||||||
|
"support": "Suporte",
|
||||||
|
"swedish": "Svenska",
|
||||||
|
"volunteerTranslator": "Ajude a traduzir esta página"
|
||||||
|
}
|
6
public/locales/pt-BR/errors.json
Normal file
6
public/locales/pt-BR/errors.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"notFoundTitle": "404 Página não encontrada",
|
||||||
|
"notFoundDescription": "Não conseguimos encontrar a página que você está procurando.",
|
||||||
|
"goToHome": "Ir para o início",
|
||||||
|
"startChat": "Iniciar chat"
|
||||||
|
}
|
36
public/locales/pt-BR/homepage.json
Normal file
36
public/locales/pt-BR/homepage.json
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"3Ls": "Sim—com 3 <e>L</e>s",
|
||||||
|
"adFree": "Sem anúncios",
|
||||||
|
"adFreeDescription": "Você pode dar um descanso ao seu bloqueador de anúncios — Você não vai precisar dele aqui.",
|
||||||
|
"comments": "Comentários",
|
||||||
|
"commentsDescription": "Participantes podem comentar na sua enquete e os comentários ficarão visíveis para todos.",
|
||||||
|
"features": "Funcionalidades",
|
||||||
|
"featuresSubheading": "Agendamento, da maneira inteligente",
|
||||||
|
"follow": "Seguir",
|
||||||
|
"getStarted": "Comece agora",
|
||||||
|
"heroSubText": "Encontre o dia certo sem contratempos",
|
||||||
|
"heroText": "Agende<br/><s>reuniões</s><br />com facilidade",
|
||||||
|
"links": "Links",
|
||||||
|
"liveDemo": "Demonstração ao vivo",
|
||||||
|
"metaDescription": "Crie enquetes e vote para encontrar o melhor dia ou hora. Uma alternativa gratuita ao Doodle.",
|
||||||
|
"metaTitle": "Rallly - Agende reuniões de grupo",
|
||||||
|
"mobileFriendly": "Compatível com dispositivos móveis",
|
||||||
|
"mobileFriendlyDescription": "Funciona muito bem em dispositivos móveis para que os participantes possam responder às enquetes onde estiverem.",
|
||||||
|
"new": "Novo",
|
||||||
|
"noLoginRequired": "Não requer login",
|
||||||
|
"noLoginRequiredDescription": "Você não precisa fazer login para criar ou participar de uma enquete",
|
||||||
|
"notifications": "Notificações",
|
||||||
|
"notificationsDescription": "Saiba quem respondeu. Seja notificado quando os participantes votarem ou comentarem na sua enquete.",
|
||||||
|
"openSource": "Código aberto",
|
||||||
|
"openSourceDescription": "O projeto é totalmente de código aberto e <a>disponível no GitHub</a>.",
|
||||||
|
"participant": "Participante",
|
||||||
|
"participantCount_other": "{{count}} participantes",
|
||||||
|
"participantCount": "{{count}} participante",
|
||||||
|
"perfect": "Perfeito!",
|
||||||
|
"principles": "Princípios",
|
||||||
|
"principlesSubheading": "Não somos como os outros",
|
||||||
|
"selfHostable": "Auto-hospedável",
|
||||||
|
"selfHostableDescription": "Execute em seu próprio servidor para ter controle total dos seus dados",
|
||||||
|
"timeSlots": "Intervalos de tempo",
|
||||||
|
"timeSlotsDescription": "Defina os horários de início e fim individuais para cada opção em sua enquete. Os horários podem ser automaticamente ajustados ao fuso horário de cada participante ou podem ser definidos para ignorar completamente o fuso horário."
|
||||||
|
}
|
|
@ -20,8 +20,9 @@ export const LanguageSelect: React.VoidFunctionComponent<{
|
||||||
>
|
>
|
||||||
<option value="en">{t("english")}</option>
|
<option value="en">{t("english")}</option>
|
||||||
<option value="es">{t("spanish")}</option>
|
<option value="es">{t("spanish")}</option>
|
||||||
<option value="fr">{t("french")}</option>
|
|
||||||
<option value="de">{t("german")}</option>
|
<option value="de">{t("german")}</option>
|
||||||
|
<option value="pt-BR">{t("portugueseBr")}</option>
|
||||||
|
<option value="fr">{t("french")}</option>
|
||||||
<option value="sv">{t("swedish")}</option>
|
<option value="sv">{t("swedish")}</option>
|
||||||
</select>
|
</select>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
const supportedLocales = ["en", "es", "de", "fr", "sv"];
|
const supportedLocales = ["en", "es", "de", "fr", "pt-BR", "sv"];
|
||||||
|
|
||||||
export function middleware({ headers, cookies, nextUrl }: NextRequest) {
|
export function middleware({ headers, cookies, nextUrl }: NextRequest) {
|
||||||
const locale =
|
const language =
|
||||||
cookies.get("NEXT_LOCALE") ??
|
cookies.get("NEXT_LOCALE") ??
|
||||||
(headers
|
(headers
|
||||||
.get("accept-language")
|
.get("accept-language")
|
||||||
|
@ -14,8 +14,11 @@ export function middleware({ headers, cookies, nextUrl }: NextRequest) {
|
||||||
|
|
||||||
const newUrl = nextUrl.clone();
|
const newUrl = nextUrl.clone();
|
||||||
|
|
||||||
if (supportedLocales.includes(locale)) {
|
if (supportedLocales.includes(language)) {
|
||||||
newUrl.pathname = `/${locale}${newUrl.pathname}`;
|
newUrl.pathname = `/${language}${newUrl.pathname}`;
|
||||||
|
} else if (language === "pt") {
|
||||||
|
// For now we send all portuguese language requests to pt-BR
|
||||||
|
newUrl.pathname = `/pt-BR${newUrl.pathname}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.rewrite(newUrl);
|
return NextResponse.rewrite(newUrl);
|
||||||
|
|
|
@ -50,6 +50,11 @@ const dayjsLocales: Record<
|
||||||
timeFormat: "24h",
|
timeFormat: "24h",
|
||||||
import: () => import("dayjs/locale/sv"),
|
import: () => import("dayjs/locale/sv"),
|
||||||
},
|
},
|
||||||
|
"pt-BR": {
|
||||||
|
weekStartsOn: "sunday",
|
||||||
|
timeFormat: "24h",
|
||||||
|
import: () => import("dayjs/locale/pt-br"),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
dayjs.extend(localizedFormat);
|
dayjs.extend(localizedFormat);
|
||||||
|
@ -84,17 +89,17 @@ export const DayjsProvider: React.VoidFunctionComponent<{
|
||||||
|
|
||||||
// Using language instead of router.locale because when transitioning from homepage to
|
// Using language instead of router.locale because when transitioning from homepage to
|
||||||
// the app via <Link locale={false}> it will be set to "en" instead of the current locale.
|
// the app via <Link locale={false}> it will be set to "en" instead of the current locale.
|
||||||
const locale = i18n.language;
|
const localeConfig = dayjsLocales[i18n.language];
|
||||||
|
|
||||||
const [weekStartsOn = dayjsLocales[locale].weekStartsOn, , setWeekStartsOn] =
|
const [weekStartsOn = localeConfig.weekStartsOn, , setWeekStartsOn] =
|
||||||
useLocalStorage<StartOfWeek>("rallly-week-starts-on");
|
useLocalStorage<StartOfWeek>("rallly-week-starts-on");
|
||||||
|
|
||||||
const [timeFormat = dayjsLocales[locale].timeFormat, setTimeFormat] =
|
const [timeFormat = localeConfig.timeFormat, setTimeFormat] =
|
||||||
useLocalStorage<TimeFormat>("rallly-time-format");
|
useLocalStorage<TimeFormat>("rallly-time-format");
|
||||||
|
|
||||||
const { value: dayjsLocale } = useAsync(async () => {
|
const { value: dayjsLocale } = useAsync(async () => {
|
||||||
return await dayjsLocales[locale ?? "en"].import();
|
return await localeConfig.import();
|
||||||
}, [locale]);
|
}, [localeConfig]);
|
||||||
|
|
||||||
if (!dayjsLocale) {
|
if (!dayjsLocale) {
|
||||||
// wait for locale to load before rendering content
|
// wait for locale to load before rendering content
|
||||||
|
@ -103,19 +108,26 @@ export const DayjsProvider: React.VoidFunctionComponent<{
|
||||||
|
|
||||||
dayjs.locale({
|
dayjs.locale({
|
||||||
...dayjsLocale,
|
...dayjsLocale,
|
||||||
weekStart: weekStartsOn ? (weekStartsOn === "monday" ? 1 : 0) : undefined,
|
weekStart: weekStartsOn
|
||||||
formats: {
|
? weekStartsOn === "monday"
|
||||||
|
? 1
|
||||||
|
: 0
|
||||||
|
: dayjsLocale.weekStart,
|
||||||
|
formats:
|
||||||
|
localeConfig.timeFormat !== timeFormat
|
||||||
|
? {
|
||||||
...dayjsLocale.formats,
|
...dayjsLocale.formats,
|
||||||
LT: timeFormat === "12h" ? "h:mm A" : "H:mm",
|
LT: timeFormat === "12h" ? "h:mm A" : "H:mm",
|
||||||
},
|
}
|
||||||
|
: dayjsLocale.formats,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DayjsContext.Provider
|
<DayjsContext.Provider
|
||||||
value={{
|
value={{
|
||||||
dayjs,
|
dayjs,
|
||||||
weekStartsOn: weekStartsOn ?? dayjsLocales[locale].weekStartsOn,
|
weekStartsOn: weekStartsOn ?? localeConfig.weekStartsOn,
|
||||||
timeFormat: timeFormat ?? dayjsLocales[locale].timeFormat,
|
timeFormat: timeFormat ?? localeConfig.timeFormat,
|
||||||
setWeekStartsOn,
|
setWeekStartsOn,
|
||||||
setTimeFormat,
|
setTimeFormat,
|
||||||
}}
|
}}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue