Skip to content

Commit

Permalink
Merge pull request #276 from Vizzuality/SKY30-375-support-language-tr…
Browse files Browse the repository at this point in the history
…anslation-for-english-french-spanish

Push and pull translations from Localazy, language selector
  • Loading branch information
clementprdhomme authored Jul 3, 2024
2 parents 41ecb92 + 80037ad commit 4d7fb09
Show file tree
Hide file tree
Showing 16 changed files with 213 additions and 21 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/localazy-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Release tag on Localazy
on:
push:
workflow_dispatch:
branches:
- main

jobs:
localazy-release:
name: Release strings to production on Localazy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: localazy/tag@v1
with:
workdir: 'frontend'
read_key: ${{ secrets.LOCALAZY_READ_KEY }}
write_key: ${{ secrets.LOCALAZY_WRITE_KEY }}
promote_from: 'latest'
promote_to: 'production'
18 changes: 18 additions & 0 deletions .github/workflows/localazy-upload.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Upload to Localazy
on:
push:
workflow_dispatch:
branches:
- develop

jobs:
localazy-upload:
name: Upload strings to Localazy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: localazy/upload@v1
with:
workdir: 'frontend'
read_key: ${{ secrets.LOCALAZY_READ_KEY }}
write_key: ${{ secrets.LOCALAZY_WRITE_KEY }}
2 changes: 2 additions & 0 deletions frontend/.env.default
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ NEXT_PUBLIC_GOOGLE_ANALYTICS=
NEXT_PUBLIC_MAPBOX_API_TOKEN=
HUBSPOT_TOKEN=

LOCALAZY_CDN=

NEXT_PUBLIC_ANALYSIS_CF_URL=
NEXT_PUBLIC_FEATURE_FLAG_ANALYSIS=

Expand Down
11 changes: 11 additions & 0 deletions frontend/localazy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"readKey": "read-key",
"writeKey": "write-key",
"upload": {
"type": "json",
"files": "translations/en.json"
},
"download": {
"files": "translations/${lang}.json"
}
}
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@loaders.gl/loader-utils": "^3.4.14",
"@loaders.gl/shapefile": "^3.4.14",
"@loaders.gl/zip": "^3.4.14",
"@localazy/cdn-client": "^1.5.2",
"@mapbox/mapbox-gl-draw": "1.4.3",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
Expand Down
49 changes: 49 additions & 0 deletions frontend/src/components/charts/conservation-chart/helpers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Render a multiline text in SVG
* @param content String to render
* @param maxLineCharacters Maximum number of characters per line
*/
export const getMultilineRenderer = (content: string, maxLineCharacters: number) => {
const MultiLinetext = ({
viewBox,
offset,
}: {
viewBox: { x: number; y: number; width: number; height: number };
offset: number;
}) => {
const { x, y } = viewBox;

const words = content.split(' ');
const lines = [];
let currentLine = '';

for (let i = 0, j = words.length; i < j; i++) {
const word = words[i];
const potentialLine = currentLine.length > 0 ? `${currentLine} ${word}` : word;

if (potentialLine.length <= maxLineCharacters) {
currentLine = potentialLine;
} else {
// If `potentialLine.length === 0`, then `potentialLine === word`
lines.push(currentLine.length > 0 ? currentLine : potentialLine);
currentLine = word;
}
}

if (currentLine.length > 0) {
lines.push(currentLine);
}

return (
<text x={x + offset} y={y}>
{lines.map((line) => (
<tspan key={line} x={x + offset} dy={15}>
{line}
</tspan>
))}
</text>
);
};

return MultiLinetext;
};
5 changes: 3 additions & 2 deletions frontend/src/components/charts/conservation-chart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { cn } from '@/lib/classnames';
import { FCWithMessages } from '@/types';
import { useGetDataInfos } from '@/types/generated/data-info';

import { getMultilineRenderer } from './helpers';
import ChartLegend from './legend';
import ChartTooltip from './tooltip';

Expand Down Expand Up @@ -186,7 +187,7 @@ const ConservationChart: FCWithMessages<ConservationChartProps> = ({
</text>
<foreignObject
{...viewBox}
x={viewBox.x + 90}
x={viewBox.x + t('30x30-target').length * 7.5}
y={viewBox.y - 17}
width="160"
height="160"
Expand All @@ -212,7 +213,7 @@ const ConservationChart: FCWithMessages<ConservationChartProps> = ({
<ReferenceLine
xAxisId={1}
x={activeYearData.year + 0.4}
label={{ position: 'insideTopLeft', value: t('future-projection'), fill: '#000' }}
label={getMultilineRenderer(t('future-projection'), 15)}
stroke="#000"
/>
<XAxis
Expand Down
29 changes: 26 additions & 3 deletions frontend/src/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useTranslations } from 'next-intl';

import ActiveLink from '@/components/active-link';
import Icon from '@/components/ui/icon';
import { Select, SelectTrigger, SelectContent, SelectItem } from '@/components/ui/select';
import {
Sheet,
SheetContent,
Expand Down Expand Up @@ -83,9 +84,8 @@ const Header: FCWithMessages<HeaderProps> = ({ theme, hideLogo = false }) => {
const [mapSettings] = useSyncMapSettings();
const [mapLayers] = useSyncMapLayers();
const [mapLayerSettings] = useSyncMapLayerSettings();
const {
query: { locationCode = 'GLOB' },
} = useRouter();
const { pathname, asPath, query, locale, push } = useRouter();
const { locationCode = 'GLOB' } = query;

const navigationEntries = useMemo(() => {
return navigationItems.map(({ name, href, colorClassName, preserveMapParams }) => {
Expand All @@ -112,6 +112,27 @@ const Header: FCWithMessages<HeaderProps> = ({ theme, hideLogo = false }) => {
});
}, [navigationItems, locationCode, mapSettings, mapLayers, mapLayerSettings]);

const languageSelector = (
<Select
value={locale}
onValueChange={(newLocale) => push({ pathname, query }, asPath, { locale: newLocale })}
>
<SelectTrigger variant="alternative">
<span className="sr-only">
{t('selected-language', {
language: locale === 'es' ? t('spanish') : locale === 'fr' ? t('french') : t('english'),
})}
</span>
<span className="not-sr-only">{locale.toLocaleUpperCase()}</span>
</SelectTrigger>
<SelectContent>
<SelectItem value="en">English{locale !== 'en' && ` (${t('english')})`}</SelectItem>
<SelectItem value="es">Español{locale !== 'es' && ` (${t('spanish')})`}</SelectItem>
<SelectItem value="fr">Français{locale !== 'fr' && ` (${t('french')})`}</SelectItem>
</SelectContent>
</Select>
);

return (
<header className={cn('border-b font-mono text-sm', headerVariants({ theme }))}>
<nav
Expand Down Expand Up @@ -169,6 +190,7 @@ const Header: FCWithMessages<HeaderProps> = ({ theme, hideLogo = false }) => {
{name}
</ActiveLink>
))}
<div className="-mx-3">{languageSelector}</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -198,6 +220,7 @@ const Header: FCWithMessages<HeaderProps> = ({ theme, hideLogo = false }) => {
</ActiveLink>
</li>
))}
<li>{languageSelector}</li>
</ul>
</nav>
</header>
Expand Down
41 changes: 34 additions & 7 deletions frontend/src/components/ui/select.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';

import * as SelectPrimitive from '@radix-ui/react-select';
import { VariantProps, cva } from 'class-variance-authority';
import { Check, ChevronDown, ChevronUp } from 'lucide-react';

import { cn } from '@/lib/classnames';
Expand All @@ -11,21 +12,47 @@ const SelectGroup = SelectPrimitive.Group;

const SelectValue = SelectPrimitive.Value;

const selectTriggerVariants = cva(
'dark:bg-slate-950 dark:ring-offset-slate-950 flex gap-x-1 w-full items-center px-3 py-2 ring-offset-white placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-gray-300 dark:placeholder:text-slate-400 dark:focus:ring-slate-300 [&>span]:line-clamp-1',
{
variants: {
variant: {
default:
'h-10 justify-between border border-black bg-white text-xs font-mono font-medium dark:border-slate-800',
alternative: 'justify-start',
},
},
defaultVariants: {
variant: 'default',
},
}
);

const selectTriggerIconVarians = cva('h-4 w-4', {
variants: {
variant: {
default: 'opacity-50',
alternative: '',
},
},
defaultVariants: {
variant: 'default',
},
});

const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> &
VariantProps<typeof selectTriggerVariants>
>(({ className, variant, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
'dark:bg-slate-950 dark:ring-offset-slate-950 flex h-10 w-full items-center justify-between border border-black bg-white px-3 py-2 font-mono text-xs font-medium ring-offset-white placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-gray-300 dark:border-slate-800 dark:placeholder:text-slate-400 dark:focus:ring-slate-300 [&>span]:line-clamp-1',
className
)}
className={cn(selectTriggerVariants({ variant }), className)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
<ChevronDown className={cn(selectTriggerIconVarians({ variant }))} />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useMemo, useState } from 'react';

import { useRouter } from 'next/router';

import FiltersButton from '@/components/filters-button';
import TooltipButton from '@/components/tooltip-button';
import { applyFilters } from '@/containers/map/content/details/helpers';
import Table from '@/containers/map/content/details/table';
Expand Down Expand Up @@ -130,6 +131,7 @@ NationalHighseasTable.messages = [
// Dependencies of `useColumns`
...SortingButton.messages,
...TooltipButton.messages,
...FiltersButton.messages,
];

export default NationalHighseasTable;
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ const MarineConservationWidget: FCWithMessages<MarineConservationWidgetProps> =
<div className="mt-6 mb-4 flex flex-col">
<span className="space-x-1">
{t.rich('marine-protected-percentage', {
b1: (chunks) => <span className="text-[64px] font-bold leading-[80%]">{chunks}</span>,
b1: (chunks) => <span className="text-[64px] font-bold leading-[90%]">{chunks}</span>,
b2: (chunks) => <span className="text-lg">{chunks}</span>,
percentage: stats?.protectedPercentage,
})}
Expand Down
36 changes: 31 additions & 5 deletions frontend/src/lib/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CdnClient } from '@localazy/cdn-client';
import deepmerge from 'deepmerge';
import { pick } from 'lodash-es';
import { useTranslations } from 'next-intl';
Expand All @@ -6,12 +7,37 @@ export const fetchTranslations = async (
locale: string,
pickedMessages?: Parameters<typeof useTranslations>[0][]
) => {
// TODO: fetch the translations from the CDN
const messages = (await import(`../../../translations/${locale}.json`)).default;
const defaultMessages = (await import(`../../../translations/en.json`)).default;
let mergedMessages;

// Use the English language as a fallback
const mergedMessages = deepmerge(defaultMessages, messages);
const isDefaultLocale = locale === 'en';

if (process.env.LOCALAZY_CDN?.length > 0) {
const cdn = await CdnClient.create({
metafile: process.env.LOCALAZY_CDN,
});

const messages = await cdn.fetch({
files: cdn.metafile.files.find(({ file }) => file !== 'strapi.json'),
locales: isDefaultLocale ? locale : ['en', locale],
});

if (isDefaultLocale) {
mergedMessages = messages;
} else {
// Use the English language as a fallback
mergedMessages = deepmerge(messages['en'], messages[locale]);
}
} else {
const defaultMessages = (await import(`../../../translations/en.json`)).default;

if (isDefaultLocale) {
mergedMessages = defaultMessages;
} else {
const messages = (await import(`../../../translations/${locale}.json`)).default;
// Use the English language as a fallback
mergedMessages = deepmerge(defaultMessages, messages);
}
}

if (pickedMessages) {
return pick(mergedMessages, pickedMessages);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/conservation-builder/[locationCode].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => {

if (mapParams) {
const searchParams = mapParamsToSearchParams(mapParams);
const target = `${PAGES.conservationBuilder}/${location}?${searchParams}`;
const target = `/${context.locale}/${PAGES.conservationBuilder}/${location}?${searchParams}`;

return {
redirect: {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/progress-tracker/[locationCode].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => {

if (mapParams) {
const searchParams = mapParamsToSearchParams(mapParams);
const target = `${PAGES.progressTracker}/${location}?${searchParams}`;
const target = `/${context.locale}/${PAGES.progressTracker}/${location}?${searchParams}`;

return {
redirect: {
Expand Down
6 changes: 5 additions & 1 deletion frontend/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,11 @@
"global": "Global",
"open-main-menu": "Open main menu",
"main-menu": "Main menu",
"close": "Close"
"close": "Close",
"selected-language": "Selected language: {language}",
"english": "English",
"spanish": "Spanish",
"french": "French"
},
"footer": {
"title": "Would you like to know more?",
Expand Down
8 changes: 8 additions & 0 deletions frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,13 @@ __metadata:
languageName: node
linkType: hard

"@localazy/cdn-client@npm:^1.5.2":
version: 1.5.2
resolution: "@localazy/cdn-client@npm:1.5.2"
checksum: 0bd10089fd4d305bb70690a50ac90d934340c1173dafb0d2b122719bca948ce9072770adfe93bfdcc628255d23bcc2ddf2cc9554ea28db1df3589a9d9414bf40
languageName: node
linkType: hard

"@luma.gl/constants@npm:8.5.21, @luma.gl/constants@npm:^8.5.21":
version: 8.5.21
resolution: "@luma.gl/constants@npm:8.5.21"
Expand Down Expand Up @@ -12065,6 +12072,7 @@ __metadata:
"@loaders.gl/loader-utils": ^3.4.14
"@loaders.gl/shapefile": ^3.4.14
"@loaders.gl/zip": ^3.4.14
"@localazy/cdn-client": ^1.5.2
"@mapbox/mapbox-gl-draw": 1.4.3
"@radix-ui/react-accordion": ^1.1.2
"@radix-ui/react-checkbox": ^1.0.4
Expand Down

0 comments on commit 4d7fb09

Please sign in to comment.