Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion echo/frontend/lingui.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const config: LinguiConfig = {
fallbackLocales: {
default: "en-US",
},
locales: ["en-US", "nl-NL", "de-DE", "fr-FR", "es-ES"],
locales: ["en-US", "nl-NL", "de-DE", "fr-FR", "es-ES", "it-IT"],
sourceLocale: "en-US",
};

Expand Down
3 changes: 2 additions & 1 deletion echo/frontend/src/components/announcement/utils/dateUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { formatRelative } from "date-fns";
import { de, enUS, es, fr, nl } from "date-fns/locale";
import { de, enUS, es, fr, it, nl } from "date-fns/locale";
import { useLanguage } from "@/hooks/useLanguage";

// Map of supported locales to date-fns locales
Expand All @@ -8,6 +8,7 @@ const localeMap = {
"en-US": enUS,
"es-ES": es,
"fr-FR": fr,
"it-IT": it,
"nl-NL": nl,
} as const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const LANGUAGE_TO_LOCALE: Record<string, string> = {
en: "en-US",
es: "es-ES",
fr: "fr-FR",
it: "it-IT",
nl: "nl-NL",
};

Expand Down
6 changes: 6 additions & 0 deletions echo/frontend/src/components/language/LanguagePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ const data: Array<{
label: "Français",
language: "fr-FR",
},
{
flag: "🇮🇹",
iso639_1: "it",
label: "Italiano",
language: "it-IT",
},
{
flag: "🇪🇸",
iso639_1: "es",
Expand Down
10 changes: 10 additions & 0 deletions echo/frontend/src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
IconNotes,
IconSettings,
IconShieldLock,
IconWorld,
} from "@tabler/icons-react";
Comment on lines +11 to 12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Harden external menu links with rel="noreferrer noopener".

The new “Help us translate” link (and the existing Documentation link) open a new tab without rel, which leaves window.opener wired up. Easy win to lock this down:

- <Menu.Item
-   rightSection={<IconNotes />}
-   component="a"
-   href={docUrl}
-   target="_blank"
- >
+ <Menu.Item
+   rightSection={<IconNotes />}
+   component="a"
+   href={docUrl}
+   target="_blank"
+   rel="noreferrer noopener"
+ >
@@
- <Menu.Item
-   rightSection={<IconWorld />}
-   component="a"
-   href="https://tally.so/r/PdprZV"
-   target="_blank"
- >
+ <Menu.Item
+   rightSection={<IconWorld />}
+   component="a"
+   href="https://tally.so/r/PdprZV"
+   target="_blank"
+   rel="noreferrer noopener"
+ >

Also applies to: 175-185, 188-195

🤖 Prompt for AI Agents
In echo/frontend/src/components/layout/Header.tsx around lines 11-12 (and also
apply to blocks at 175-185 and 188-195), external links that open in a new tab
(target="_blank") are missing the rel attribute; update each anchor that opens a
new tab (e.g., Documentation and Help us translate links) to include
rel="noreferrer noopener" to prevent window.opener from being exposed and
improve security; ensure you add both values exactly as shown and keep other
attributes intact.

import { useParams } from "react-router";
import {
Expand Down Expand Up @@ -184,6 +185,15 @@ const HeaderView = ({ isAuthenticated, loading }: HeaderViewProps) => {

<CreateFeedbackButton />

<Menu.Item
rightSection={<IconWorld />}
component="a"
href="https://tally.so/r/PdprZV"
target="_blank"
>
<Trans>Help us translate</Trans>
</Menu.Item>

<Menu.Item
rightSection={<IconLogout />}
onClick={handleLogout}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,31 @@ const ParticipantOnboardingCards = ({ project }: { project: Project }) => {
],
},
],
"it-IT": [
...getSystemCards("it-IT", tutorialSlug),
{
section: "Controllo Microfono",
slides: [
{
component: MicrophoneTestComponent,
content: "Assicuriamoci di poterti sentire.",
icon: IconMicrophone,
title: "Controllo Microfono",
type: "microphone",
},
],
},
{
section: "Pronti a iniziare?",
slides: [
{
component: InitiateFormComponent,
icon: Play,
title: "Pronti a iniziare?",
},
],
},
],
"nl-NL": [
...getSystemCards("nl-NL", tutorialSlug),
{
Expand Down
193 changes: 193 additions & 0 deletions echo/frontend/src/components/participant/hooks/useOnboardingCards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,75 @@ export const useOnboardingCards = () => {
],
},
],
"it-IT": [
{
section: "Benvenuto",
slides: [
{
content:
"Registra la tua voce per rispondere alle domande e avere un impatto.",
cta: "Andiamo!",
extraHelp:
"Questo è un mini-tutorial. Usa i pulsanti precedente e successivo per navigare. Al termine entrerai nel portale di registrazione.",
icon: PartyPopper,
title: "Benvenuto su Dembrane!",
},
{
content:
"Dembrane aiuta a raccogliere facilmente contributi da grandi gruppi.",
cta: "Dimmi di più",
extraHelp:
"Che si tratti di feedback per un comune, di input sul lavoro o di partecipazione a una ricerca, la tua voce conta!",
icon: Orbit,
title: "Cos'è Dembrane?",
},
{
content:
"Rispondi alle domande a tuo ritmo parlando o scrivendo.",
cta: "Avanti",
extraHelp:
"La voce è la modalità principale perché permette risposte più naturali e ricche. Scrivere è sempre disponibile come alternativa.",
icon: Speech,
title: "Dì semplicemente ciò che pensi",
},
{
content: "Dembrane è più divertente in gruppo!",
cta: "Avanti",
extraHelp:
"È ancora meglio se trovi qualcuno con cui discutere le domande e registrare la conversazione. Non possiamo sapere chi ha detto cosa, solo quali idee sono state condivise.",
icon: MessagesSquare,
title: "Da soli o in gruppo",
},
],
},
{
section: "Come funziona",
slides: [
{
content:
"Riceverai le domande quando sarai nel portale di registrazione.",
cta: "Capito",
extraHelp:
"Le domande variano in base alle esigenze dell'host. Possono riguardare temi della comunità, esperienze di lavoro o ricerca. Se non ci sono domande specifiche, puoi condividere qualsiasi pensiero o preoccupazione.",
icon: HelpCircle,
title: "È il momento delle domande",
},
],
},
{
section: "Privacy",
slides: [
{
content: "Come registratore, controlli tu ciò che condividi.",
cta: "Dimmi di più",
extraHelp:
"Evita di condividere dettagli che non vuoi rendere noti all'host. Chiedi sempre il consenso prima di registrare altre persone.",
icon: Lock,
title: "La privacy conta",
},
],
},
],
"nl-NL": [
{
section: "Welkom",
Expand Down Expand Up @@ -816,6 +885,108 @@ export const useOnboardingCards = () => {
],
},
],
"it-IT": [
{
section: "Benvenuto",
slides: [
{
content:
"Registra la tua voce per rispondere alle domande e avere un impatto.",
cta: "Andiamo!",
extraHelp:
"Questo è un mini-tutorial. Usa i pulsanti precedente e successivo per navigare. Al termine entrerai nel portale di registrazione.",
icon: PartyPopper,
title: "Benvenuto su Dembrane!",
},
{
content:
"Dembrane aiuta a raccogliere facilmente contributi da grandi gruppi.",
cta: "Dimmi di più",
extraHelp:
"Che si tratti di feedback per un comune, di input sul lavoro o di partecipazione a una ricerca, la tua voce conta!",
icon: Orbit,
title: "Cos'è Dembrane?",
},
{
content:
"Rispondi alle domande a tuo ritmo parlando o scrivendo.",
cta: "Avanti",
extraHelp:
"La voce è la modalità principale perché permette risposte più naturali e ricche. Scrivere è sempre disponibile come alternativa.",
icon: Speech,
title: "Dì semplicemente ciò che pensi",
},
{
content: "Dembrane è più divertente in gruppo!",
cta: "Avanti",
extraHelp:
"È ancora meglio se trovi qualcuno con cui discutere le domande e registrare la conversazione. Non possiamo sapere chi ha detto cosa, solo quali idee sono state condivise.",
icon: MessagesSquare,
title: "Da soli o in gruppo",
},
],
},
{
section: "Come funziona",
slides: [
{
content:
"Riceverai le domande quando sarai nel portale di registrazione.",
cta: "Capito",
extraHelp:
"Le domande variano in base alle esigenze dell'host. Possono riguardare temi della comunità, esperienze di lavoro o ricerca. Se non ci sono domande specifiche, puoi condividere qualsiasi pensiero o preoccupazione.",
icon: HelpCircle,
title: "È il momento delle domande",
},
],
},
{
section: "Privacy",
slides: [
{
content: "Come registratore, controlli tu ciò che condividi.",
cta: "Dimmi di più",
extraHelp:
"Evita di condividere dettagli che non vuoi rendere noti all'host. Chiedi sempre il consenso prima di registrare altre persone.",
icon: Lock,
title: "La privacy conta",
},
...(getPrivacyCard("it-IT")?.slides || []),
],
},
{
section: "Migliori Pratiche",
slides: [
{
content:
"Immagina che Dembrane sia in vivavoce con te. Se riesci a sentirti, sei a posto.",
cta: "Capito",
extraHelp:
"Un po' di rumore di fondo va bene, purché sia chiaro chi sta parlando.",
icon: Volume2,
title: "Riduci il Rumore di Fondo",
},
{
content:
"Assicurati di avere una connessione stabile per una registrazione fluida.",
cta: "Pronto!",
extraHelp:
"Wi-Fi o buoni dati mobili funzionano meglio. Se la connessione cade, non preoccuparti. Puoi sempre riprendere da dove avevi interrotto.",
icon: Wifi,
title: "Connessione Internet Forte",
},
{
content:
"Evita interruzioni mantenendo il dispositivo sbloccato. Se si blocca, sbloccalo semplicemente e continua.",
cta: "Okay",
extraHelp:
"Dembrane cerca di mantenere il dispositivo attivo, ma a volte i dispositivi possono sovrascrivere questa impostazione. Puoi regolare le impostazioni del dispositivo per rimanere sbloccato più a lungo se necessario.",
icon: Smartphone,
title: "Non bloccare il dispositivo!",
},
],
},
],
"nl-NL": [
{
section: "Welkom",
Expand Down Expand Up @@ -1016,6 +1187,28 @@ export const useOnboardingCards = () => {
},
],
},
"it-IT": {
section: "Privacy",
slides: [
{
checkbox: {
label: "Accetto l'informativa sulla privacy",
required: true,
},
content:
"I tuoi dati sono archiviati in modo sicuro, analizzati e mai condivisi con terze parti.",
cta: "Ho capito",
extraHelp:
"Le registrazioni vengono trascritte e analizzate per ottenere insight, poi eliminate dopo 30 giorni. Per dettagli specifici, contatta l'host che ti ha fornito il QR code.",
icon: Server,
link: {
label: "Leggi l'informativa completa sulla privacy",
url: "https://dembrane.notion.site/Privacy-Statement-Dembrane-1439cd84270580748046cc589861d115",
},
title: "Uso dei dati e sicurezza",
},
],
},
"nl-NL": {
section: "Privacy",
slides: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const VerifiedArtefactsList = ({
en: "en-US",
es: "es-ES",
fr: "fr-FR",
it: "it-IT",
nl: "nl-NL",
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import {
} from "./hooks";
import { VerifyInstructions } from "./VerifyInstructions";

type LanguageCode = "de" | "en" | "es" | "fr" | "nl";
type LanguageCode = "de" | "en" | "es" | "fr" | "nl" | "it";

const LANGUAGE_TO_LOCALE: Record<LanguageCode, string> = {
de: "de-DE",
en: "en-US",
es: "es-ES",
fr: "fr-FR",
it: "it-IT",
nl: "nl-NL",
};

Expand Down
6 changes: 4 additions & 2 deletions echo/frontend/src/components/project/ProjectPortalEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,20 @@ const FormSchema = z.object({
is_get_reply_enabled: z.boolean(),
is_project_notification_subscription_allowed: z.boolean(),
is_verify_enabled: z.boolean(),
language: z.enum(["en", "nl", "de", "fr", "es"]),
language: z.enum(["en", "nl", "de", "fr", "es", "it"]),
verification_topics: z.array(z.string()),
});

type ProjectPortalFormValues = z.infer<typeof FormSchema>;

type LanguageCode = "de" | "en" | "es" | "fr" | "nl";
type LanguageCode = "de" | "en" | "es" | "fr" | "nl" | "it";

const LANGUAGE_TO_LOCALE: Record<LanguageCode, string> = {
de: "de-DE",
en: "en-US",
es: "es-ES",
fr: "fr-FR",
it: "it-IT",
nl: "nl-NL",
};

Comment on lines +53 to 69
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Unify LanguageCode usage with projectLanguageCode for type safety.

You’ve extended the schema, LanguageCode, and LANGUAGE_TO_LOCALE to include "it", which is exactly what we want. The only loose end is projectLanguageCode still hardcoding a narrower union; you can let the single source of truth drive it:

-  const projectLanguageCode = (project.language ?? "en") as
-    | "en"
-    | "nl"
-    | "de"
-    | "fr"
-    | "es";
+  const projectLanguageCode = (project.language ?? "en") as LanguageCode;

Keeps runtime behavior the same, but keeps the type system honest when more languages get added.

Also applies to: 197-203

Expand Down Expand Up @@ -465,6 +466,7 @@ const ProjectPortalEditorComponent: React.FC<ProjectPortalEditorProps> = ({
{ label: t`German`, value: "de" },
{ label: t`Spanish`, value: "es" },
{ label: t`French`, value: "fr" },
{ label: t`Italian`, value: "it" },
]}
{...field}
/>
Expand Down
4 changes: 4 additions & 0 deletions echo/frontend/src/components/project/ProjectQRCode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export const useProjectSharingLink = (project?: Project) => {
"es-ES": "es-ES",
fr: "fr-FR",
"fr-FR": "fr-FR",
it: "it-IT",
"it-IT": "it-IT",
nl: "nl-NL",
"nl-NL": "nl-NL",
}[
Expand All @@ -46,11 +48,13 @@ export const useProjectSharingLink = (project?: Project) => {
| "de"
| "fr"
| "es"
| "it"
| "en-US"
| "nl-NL"
| "de-DE"
| "fr-FR"
| "es-ES"
| "it-IT"
];

const link = `${PARTICIPANT_BASE_URL}/${languageCode}/${project.id}/start`;
Expand Down
Loading
Loading