Skip to content

Commit

Permalink
feat(locale): language switching (#215)
Browse files Browse the repository at this point in the history
* feat(language): Add default redirect functionality to ES

Implemented functionality to redirect to Spanish by default

* feat(language): add language change button

The implemented functionality added a language button with options for Spanish (ES) and English (EN).

* refactor: reduce coupling and magic strings

* fix: refactor data exchange signature

* refactor: add last name boolean filtering

fixes the " undefined" on `last` trait when second last name was `undefined`

* fix (cicd): Adding right permissions to pipelines

* fix (cicd): Adding right permissions to pipelines

* Update cd-deploy-to-prod.yml
* Update cd-deploy-to-test.yml
* Update ci-check-linters.yml
* Update ci-unit-tests.yml
* Update delete-deployment.yml
* Update sub-build-push-image.yml
* Update sub-cloudrun-deploy.yml

* fix(router): change route redirection method

Replaced router.push with window.location.href to address caching issue when changing language.

* bump: v1.1.3

* fix: remove direct language reference

---------

Co-authored-by: Jeffrey Mesa <[email protected]>
Co-authored-by: Kevin Jimenez <[email protected]>
Co-authored-by: Marluan Espiritusanto <[email protected]>
  • Loading branch information
4 people authored May 15, 2024
1 parent 25700b5 commit 863a1c6
Show file tree
Hide file tree
Showing 22 changed files with 103 additions and 25 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/sub-cloudrun-deploy.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
name: Deploy to Cloud Run

permissions: read-all

on:
workflow_call:
inputs:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "cuenta-unica-registry",
"description": "Portal de registro de Cuenta Única",
"version": "v1.1.2",
"version": "v1.1.3",
"private": false,
"author": "OGTIC",
"license": "MIT",
Expand Down
9 changes: 9 additions & 0 deletions public/assets/en-language.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions public/assets/es-language.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/app/[lang]/identification/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function Form() {
watch,
} = useForm<CedulaForm>({
reValidateMode: 'onSubmit',
resolver: zodResolver(createCedulaSchema({ intl })),
resolver: zodResolver(createCedulaSchema(intl)),
});

const cedulaFormValue = watch('cedula', '');
Expand Down
2 changes: 1 addition & 1 deletion src/app/[lang]/liveness/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function Form({ cedula }: Props) {
register,
formState: { errors },
} = useForm<TermsForm>({
resolver: zodResolver(createTermsSchema({ intl })),
resolver: zodResolver(createTermsSchema(intl)),
});

const onSubmit = handleSubmit(() => setOpen(true));
Expand Down
11 changes: 8 additions & 3 deletions src/app/[lang]/provider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use client';
import { createContext, useContext } from 'react';
import { createContext, use } from 'react';

import { getDictionary } from '@/dictionaries';
import { i18n } from '@/i18n-config';

type Props = {
children: React.ReactNode;
Expand All @@ -11,6 +12,7 @@ type Props = {
export function LanguageProvider({ children, intl }: Props) {
const contextValue = {
intl,
locales: i18n.locales,
};

return (
Expand All @@ -21,7 +23,7 @@ export function LanguageProvider({ children, intl }: Props) {
}

export function useLanguage() {
const context = useContext(LanguageContext);
const context = use(LanguageContext);

if (!context) {
throw new Error('useLanguage must be used within a LanguageProvider');
Expand All @@ -32,4 +34,7 @@ export function useLanguage() {

export const LanguageContext = createContext({} as Context);

export type Context = { intl: Props['intl'] };
export type Context = {
intl: Props['intl'];
locales: (typeof i18n)['locales'];
};
8 changes: 5 additions & 3 deletions src/app/[lang]/register/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export function Form({ cedula }: Props) {
watch,
} = useForm<RegisterForm>({
mode: 'onChange',
resolver: zodResolver(createRegisterSchema({ intl }, cedula)),
resolver: zodResolver(createRegisterSchema(intl, cedula)),
});

const password = watch('password');
Expand Down Expand Up @@ -145,7 +145,9 @@ export function Form({ cedula }: Props) {
username: citizen.id,
name: {
first: citizen.names,
last: [citizen.firstSurname, citizen.secondSurname].join(' '),
last: [citizen.firstSurname, citizen.secondSurname]
.filter(Boolean)
.join(' '),
},
birthdate: citizen.birthDate,
gender: citizen.gender,
Expand All @@ -163,7 +165,7 @@ export function Form({ cedula }: Props) {
);

if (item) {
return router.push(`/en/verification?flow=${item?.flow.id}`);
return router.push(`/verification?flow=${item?.flow.id}`);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/app/[lang]/verification/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function Form({ flow, returnTo, code }: Props) {

const { handleSubmit, setValue } = useForm<VerificationForm>({
reValidateMode: 'onSubmit',
resolver: zodResolver(createVerificationSchema({ intl })),
resolver: zodResolver(createVerificationSchema(intl)),
});

const [loading, setLoading] = useState(false);
Expand Down Expand Up @@ -99,7 +99,7 @@ export function Form({ flow, returnTo, code }: Props) {
// Status code 410 means the request has expired - so let's load a fresh flow!
case 403:
// Status code 403 implies some other issue (e.g. CSRF) - let's reload!
return router.push('/en/verification');
return router.push('/verification');
}

throw new Error(err);
Expand Down
2 changes: 1 addition & 1 deletion src/common/validation-schemas/cedula.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { z } from 'zod';

import { Context } from '@/app/[lang]/provider';

export const createCedulaSchema = ({ intl: { validations } }: Context) =>
export const createCedulaSchema = ({ validations }: Context['intl']) =>
z.object({
cedula: z
.string()
Expand Down
2 changes: 1 addition & 1 deletion src/common/validation-schemas/register.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { z } from 'zod';
import { Context } from '@/app/[lang]/provider';

export const createRegisterSchema = (
{ intl: { validations } }: Context,
{ validations }: Context['intl'],
cedula: string,
) =>
z
Expand Down
2 changes: 1 addition & 1 deletion src/common/validation-schemas/report.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { z } from 'zod';

import { Context } from '@/app/[lang]/provider';

export const createReportSchema = ({ intl: { validations } }: Context) =>
export const createReportSchema = ({ validations }: Context['intl']) =>
z.object({
email: z.string().email(validations.email.invalid),
name: z.string().optional(),
Expand Down
2 changes: 1 addition & 1 deletion src/common/validation-schemas/terms.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { z } from 'zod';

import { Context } from '@/app/[lang]/provider';

export const createTermsSchema = ({ intl }: Context) =>
export const createTermsSchema = (intl: Context['intl']) =>
z.object({
accepted: z.literal(true, {
required_error: intl.terms.check,
Expand Down
2 changes: 1 addition & 1 deletion src/common/validation-schemas/verification.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { z } from 'zod';

import { Context } from '@/app/[lang]/provider';

export const createVerificationSchema = ({ intl: { validations } }: Context) =>
export const createVerificationSchema = ({ validations }: Context['intl']) =>
z.object({
code: z.string().min(6, validations.code.min).max(6, validations.code.max),
});
2 changes: 1 addition & 1 deletion src/components/LivenessQuickStart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function LivenessQuickStart({ cedula }: Props) {

const { intl } = useLanguage();

const displayText = useLocalizedText({ intl });
const displayText = useLocalizedText(intl);

const fetchCreateLiveness: () => Promise<void> = async () => {
await fetch(`/api/biometric`, { method: 'POST' })
Expand Down
6 changes: 2 additions & 4 deletions src/components/LivenessQuickStart/localizedText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ import type {
} from '@aws-amplify/ui-react-liveness/dist/types/components/FaceLivenessDetector/displayText';

export const useLocalizedText = ({
intl: {
liveness: { hints, camera, instructions, stream, error },
},
}: Context) =>
liveness: { hints, camera, instructions, stream, error },
}: Context['intl']) =>
({
...({
hintMoveFaceFrontOfCameraText: hints.moveFaceFrontOfCamera,
Expand Down
2 changes: 1 addition & 1 deletion src/components/UserFeedbackModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function UserFeedbackModal({ open, onClose }: Props) {
register,
reset,
} = useForm<Report>({
resolver: zodResolver(createReportSchema({ intl })),
resolver: zodResolver(createReportSchema(intl)),
});

const sendFeedback = handleSubmit(({ email, comments, name = '' }) => {
Expand Down
45 changes: 45 additions & 0 deletions src/components/layout/languageSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Image from 'next/image';

import esicon from '@public/assets/es-language.svg';
import enicon from '@public/assets/en-language.svg';
import esdata from '../../dictionaries/es.json';
import endata from '../../dictionaries/en.json';

import { ButtonApp } from '@/components/elements/button';
import { Locale } from '@/i18n-config';

const locales = {
es: {
icon: esicon.src,
name: esdata.languageName,
},
en: {
icon: enicon.src,
name: endata.languageName,
},
};

export const LanguageSelector = ({ other }: { other: Locale }) => {
const changeLanguage = () => {
window.location.href = `/${other}/identification`;
};

return (
<div style={{ marginRight: 16 }}>
<ButtonApp
variant="outlined"
startIcon={
<Image
src={locales[other].icon}
alt="language icon"
width="24"
height="24"
/>
}
onClick={changeLanguage}
>
{locales[other].name}
</ButtonApp>
</div>
);
};
7 changes: 5 additions & 2 deletions src/components/layout/navBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import Link from 'next/link';
import Logo from '@public/assets/logo.svg';
import styles from './styles.module.css';

import { LanguageSelector } from './languageSelector';
import { useLanguage } from '@/app/[lang]/provider';
import { ButtonApp } from '../elements/button';

export default function Index() {
const { intl } = useLanguage();
const { intl, locales } = useLanguage();

return (
<>
Expand Down Expand Up @@ -41,8 +42,10 @@ export default function Index() {
color="primary"
style={{ display: 'none' }}
/>
<LanguageSelector
other={locales.find((l) => l !== intl.language)!}
/>
<ButtonApp
variant="outlined"
notFullWidth
onClick={() =>
window.open('https://mi.cuentaunica.gob.do/ui/login')
Expand Down
2 changes: 2 additions & 0 deletions src/dictionaries/en.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"languageName": "English",
"language": "en",
"common": {
"title": "Cuenta Única Ciudadana",
"hello": "Hello",
Expand Down
2 changes: 2 additions & 0 deletions src/dictionaries/es.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"languageName": "Español",
"language": "es",
"common": {
"title": "Cuenta Única Ciudadana",
"hello": "Hola",
Expand Down
3 changes: 2 additions & 1 deletion src/middlewares/i18n.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ function getLocale(request: NextRequest): string | undefined {
// Use negotiator and intl-localematcher to get best locale
let languages = new Negotiator({ headers }).languages(locales);

return matchLocale(languages, locales, i18n.defaultLocale);
// return matchLocale(languages, locales, i18n.defaultLocale);
return i18n.defaultLocale;
}

0 comments on commit 863a1c6

Please sign in to comment.