Translate portal chrome and page strings

Replace the remaining hard-coded UI copy in the top bar, user menu, home
hero, and the documents/subprocessors/updates/requests pages with i18next
keys, and supply the en-US and fr-FR catalog entries for them. The home
hero title uses value interpolation rather than string concatenation so
word order stays correct across locales.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-06-28 14:04:31 +02:00
parent 86077e6c27
commit a5e0fb59e9
8 changed files with 84 additions and 14 deletions

View File

@@ -1,5 +1,32 @@
{
"topBar": {
"tagline": "Compliance Portal",
"getAccess": "Get Access",
"nav": {
"documents": "Documents",
"subprocessors": "Subprocessors",
"updates": "Updates",
"requests": "Requests"
}
},
"userMenu": {
"signOut": "Sign out"
},
"home": {
"heroTitle": "Trust at {{name}}."
},
"documents": {
"title": "Documents"
},
"subprocessors": {
"title": "Subprocessors"
},
"updates": {
"title": "Updates",
"subscribe": "Subscribe to updates"
},
"requests": {
"title": "Data Requests",
"newRequest": "New Request"
}
}

View File

@@ -1,5 +1,32 @@
{
"topBar": {
"tagline": "Portail de conformité",
"getAccess": "Obtenir l'accès",
"nav": {
"documents": "Documents",
"subprocessors": "Sous-traitants",
"updates": "Mises à jour",
"requests": "Demandes"
}
},
"userMenu": {
"signOut": "Se déconnecter"
},
"home": {
"heroTitle": "La confiance chez {{name}}."
},
"documents": {
"title": "Documents"
},
"subprocessors": {
"title": "Sous-traitants"
},
"updates": {
"title": "Mises à jour",
"subscribe": "S'abonner aux mises à jour"
},
"requests": {
"title": "Demandes de données",
"newRequest": "Nouvelle demande"
}
}

View File

@@ -17,6 +17,7 @@ import { Avatar } from "@probo/ui/src/v2/Avatar/Avatar";
import { Button } from "@probo/ui/src/v2/Button/Button";
import { Link } from "@probo/ui/src/v2/Button/Link";
import { Text } from "@probo/ui/src/v2/typography/Text";
import { useTranslation } from "react-i18next";
import { graphql, useFragment } from "react-relay";
import { Link as RouterLink, useLocation } from "react-router";
@@ -25,10 +26,10 @@ import { TopBarUserMenu } from "./TopBarUserMenu";
import { topBar } from "./variants";
const NAV_ITEMS = [
{ to: "/documents", label: "Documents" },
{ to: "/subprocessors", label: "Subprocessors" },
{ to: "/updates", label: "Updates" },
{ to: "/requests", label: "Requests" },
{ to: "/documents", labelKey: "topBar.nav.documents" },
{ to: "/subprocessors", labelKey: "topBar.nav.subprocessors" },
{ to: "/updates", labelKey: "topBar.nav.updates" },
{ to: "/requests", labelKey: "topBar.nav.requests" },
] as const;
const topBarFragment = graphql`
@@ -57,6 +58,7 @@ function isActive(pathname: string, to: string): boolean {
}
export function TopBar({ queryKey }: TopBarProps) {
const { t } = useTranslation();
const data = useFragment(topBarFragment, queryKey);
const { pathname } = useLocation();
@@ -83,7 +85,7 @@ export function TopBar({ queryKey }: TopBarProps) {
{organizationName}
</Text>
<Text size={2} color="neutral">
Compliance Portal
{t("topBar.tagline")}
</Text>
</RouterLink>
@@ -99,13 +101,13 @@ export function TopBar({ queryKey }: TopBarProps) {
size={2}
active={isActive(pathname, item.to)}
>
{item.label}
{t(item.labelKey)}
</Link>
))}
{data.viewer == null
? (
<Button variant="solid" color="neutral" highContrast iconStart={<LockSimpleIcon />}>
Get Access
{t("topBar.getAccess")}
</Button>
)
: <TopBarUserMenu identityKey={data.viewer} />}

View File

@@ -21,6 +21,7 @@ import { DropdownPopup } from "@probo/ui/src/v2/Dropdown/DropdownPopup";
import { DropdownSeparator } from "@probo/ui/src/v2/Dropdown/DropdownSeparator";
import { DropdownTrigger } from "@probo/ui/src/v2/Dropdown/DropdownTrigger";
import { Text } from "@probo/ui/src/v2/typography/Text";
import { useTranslation } from "react-i18next";
import { graphql, useFragment } from "react-relay";
import type { TopBarUserMenu_identity$key } from "./__generated__/TopBarUserMenu_identity.graphql";
@@ -38,6 +39,7 @@ interface TopBarUserMenuProps {
}
export function TopBarUserMenu({ identityKey }: TopBarUserMenuProps) {
const { t } = useTranslation();
const identity = useFragment(topBarUserMenuFragment, identityKey);
return (
@@ -63,7 +65,7 @@ export function TopBarUserMenu({ identityKey }: TopBarUserMenuProps) {
<DropdownGroupLabel>{identity.email}</DropdownGroupLabel>
<DropdownSeparator />
<DropdownItem color="error" iconStart={<SignOutIcon />}>
Sign out
{t("userMenu.signOut")}
</DropdownItem>
</DropdownPopup>
</Dropdown>

View File

@@ -12,6 +12,7 @@
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import { useTranslation } from "react-i18next";
import type { PreloadedQuery } from "react-relay";
import { graphql, usePreloadedQuery } from "react-relay";
@@ -37,11 +38,15 @@ interface HomePageProps {
}
export function HomePage({ queryRef }: HomePageProps) {
const { t } = useTranslation();
const data = usePreloadedQuery<HomePageQuery>(homePageQuery, queryRef);
const { organization } = data.currentTrustCenter;
return (
<Hero title={`Trust at ${organization.name}.`} description={organization.description}>
<Hero
title={t("home.heroTitle", { name: organization.name })}
description={organization.description}
>
<OrganizationContactInfo organizationKey={organization} />
</Hero>
);

View File

@@ -14,16 +14,18 @@
import { PlusIcon } from "@phosphor-icons/react";
import { Button } from "@probo/ui/src/v2/Button/Button";
import { useTranslation } from "react-i18next";
import { PageHeader } from "#/components/PageHeader/PageHeader";
export default function RequestsPage() {
const { t } = useTranslation();
return (
<PageHeader
title="Data Requests"
title={t("requests.title")}
actions={(
<Button variant="soft" color="neutral" highContrast iconStart={<PlusIcon />}>
New Request
{t("requests.newRequest")}
</Button>
)}
/>

View File

@@ -12,10 +12,13 @@
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import { useTranslation } from "react-i18next";
import { PageHeader } from "#/components/PageHeader/PageHeader";
// Toolbar (category/region filters, search, Download CSV) and the subprocessor
// count are deferred until the v2 Select/TextField components exist.
export default function SubprocessorsPage() {
return <PageHeader title="Subprocessors" />;
const { t } = useTranslation();
return <PageHeader title={t("subprocessors.title")} />;
}

View File

@@ -14,16 +14,18 @@
import { BellIcon } from "@phosphor-icons/react";
import { Button } from "@probo/ui/src/v2/Button/Button";
import { useTranslation } from "react-i18next";
import { PageHeader } from "#/components/PageHeader/PageHeader";
export default function UpdatesPage() {
const { t } = useTranslation();
return (
<PageHeader
title="Updates"
title={t("updates.title")}
actions={(
<Button variant="soft" color="neutral" highContrast iconStart={<BellIcon />}>
Subscribe to updates
{t("updates.subscribe")}
</Button>
)}
/>