Skip to content

Commit

Permalink
translations in Provider (#3136)
Browse files Browse the repository at this point in the history
* 🚧 translations in Provider

* ♻️ consume providerContext translations in useI18n + example usage (FormProgress)

* changeset

* 🎨 keep component level translations

* ♻️ extract language provider into own provider

* 📝 update changeset doc

* 🎨 undo all changes to original Provider

* 📝 changeset text

* ✅ update test for i18n changes

* refactor: Moved location of LanguageProvider, removed changeset

---------

Co-authored-by: Ken <[email protected]>
  • Loading branch information
JulianNymark and KenAJoh committed Sep 12, 2024
1 parent 45a50a5 commit 46a14b1
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 17 deletions.
92 changes: 92 additions & 0 deletions @navikt/core/react/src/form/form-progress/FormProgress.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Meta, StoryFn } from "@storybook/react";
import React, { useState } from "react";
import VStack from "../../layout/stack/VStack";
import UNSAFE_AkselLanguageProvider from "../../provider/i18n/LanguageProvider";
import FormProgress, { FormProgressProps } from "./FormProgress";

export default {
Expand Down Expand Up @@ -37,6 +38,97 @@ export const Default: StoryFn<ControllableProps> = (props) => (
);
Default.args = { activeStep: 2, totalSteps: 7, interactiveSteps: true };

export const ProvidedTranslations: StoryFn = () => {
const translations = {
FormProgress: {
step: "Step {activeStep} of {totalSteps}",
showAllSteps: "Show all steps",
hideAllSteps: "Hide all steps",
},
};

return (
<UNSAFE_AkselLanguageProvider translations={translations}>
<FormProgress activeStep={2} totalSteps={7} interactiveSteps>
<FormProgress.Step href="#" completed>
Start søknad
</FormProgress.Step>
<FormProgress.Step href="#">Personopplysninger</FormProgress.Step>
<FormProgress.Step interactive={false}>
Saksopplysninger
</FormProgress.Step>
<FormProgress.Step interactive={false}>
Søknadstekst for en veldig spesifikk prosess i NAV som har lang tekst
</FormProgress.Step>
<FormProgress.Step href="#">Vedlegg</FormProgress.Step>
<FormProgress.Step href="#">Oppsummering</FormProgress.Step>
<FormProgress.Step href="#">Innsending</FormProgress.Step>
</FormProgress>
</UNSAFE_AkselLanguageProvider>
);
};

export const DefaultTranslations: StoryFn = () => {
return (
<UNSAFE_AkselLanguageProvider>
<FormProgress activeStep={2} totalSteps={7} interactiveSteps>
<FormProgress.Step href="#" completed>
Start søknad
</FormProgress.Step>
<FormProgress.Step href="#">Personopplysninger</FormProgress.Step>
<FormProgress.Step interactive={false}>
Saksopplysninger
</FormProgress.Step>
<FormProgress.Step interactive={false}>
Søknadstekst for en veldig spesifikk prosess i NAV som har lang tekst
</FormProgress.Step>
<FormProgress.Step href="#">Vedlegg</FormProgress.Step>
<FormProgress.Step href="#">Oppsummering</FormProgress.Step>
<FormProgress.Step href="#">Innsending</FormProgress.Step>
</FormProgress>
</UNSAFE_AkselLanguageProvider>
);
};

export const ComponentTranslations: StoryFn = () => {
const globalTranslations = {
FormProgress: {
step: "Step {activeStep} of {totalSteps}",
showAllSteps: "Show all steps",
hideAllSteps: "Hide all steps",
},
};
const translations = {
step: "Skref {activeStep} af {totalSteps}",
showAllSteps: "Sýndu öll skref",
hideAllSteps: "Fela öll skref",
};
return (
<UNSAFE_AkselLanguageProvider translations={globalTranslations}>
<FormProgress
translations={translations}
activeStep={2}
totalSteps={7}
interactiveSteps
>
<FormProgress.Step href="#" completed>
Start søknad
</FormProgress.Step>
<FormProgress.Step href="#">Personopplysninger</FormProgress.Step>
<FormProgress.Step interactive={false}>
Saksopplysninger
</FormProgress.Step>
<FormProgress.Step interactive={false}>
Søknadstekst for en veldig spesifikk prosess i NAV som har lang tekst
</FormProgress.Step>
<FormProgress.Step href="#">Vedlegg</FormProgress.Step>
<FormProgress.Step href="#">Oppsummering</FormProgress.Step>
<FormProgress.Step href="#">Innsending</FormProgress.Step>
</FormProgress>
</UNSAFE_AkselLanguageProvider>
);
};

export const Controlled: StoryFn = () => {
const [open, setOpen] = useState(true);
return (
Expand Down
51 changes: 51 additions & 0 deletions @navikt/core/react/src/provider/i18n/LanguageProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { createContext, useContext } from "react";
import { TranslationDictionary } from "../../util/i18n/i18n.types";
import nb from "../../util/i18n/locales/nb";

export interface LanguageProviderContextType {
/**
* Merged with the default language translations object (officially provided translations).
*/
translations: TranslationDictionary | TranslationDictionary[];
}

export const LanguageProviderContext =
createContext<LanguageProviderContextType>({
translations: nb,
});

export interface LanguageProviderProps {
children?: React.ReactNode;
translations?: TranslationDictionary | TranslationDictionary[];
}

export const useProvider = () => useContext(LanguageProviderContext);

/**
* @private Feature is under development and should not be used in any applications.
*
* @example
* ```jsx
* <UNSAFE_AkselLanguageProvider translations={{...}}>
* {app}
* </UNSAFE_AkselLanguageProvider>
* ```
*/
export const UNSAFE_AkselLanguageProvider = ({
children,
translations,
...rest
}: LanguageProviderProps) => {
return (
<LanguageProviderContext.Provider
value={{
translations: translations ?? nb,
...rest,
}}
>
{children}
</LanguageProviderContext.Provider>
);
};

export default UNSAFE_AkselLanguageProvider;
15 changes: 9 additions & 6 deletions @navikt/core/react/src/util/i18n/i18n.context.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { renderHook } from "@testing-library/react";
import React from "react";
import { describe, expect, test } from "vitest";
import { I18nContext, useI18n } from "./i18n.context";
import UNSAFE_AkselLanguageProvider from "../../provider/i18n/LanguageProvider";
import { useI18n } from "./i18n.context";

describe("useI18n", () => {
test("should throw error if key is not found", () => {
Expand All @@ -15,7 +16,9 @@ describe("useI18n", () => {
const i18n = { FileUpload: { item: { uploading: "Test translation" } } };
const { result } = renderHook(() => useI18n("FileUpload"), {
wrapper: ({ children }) => (
<I18nContext.Provider value={i18n}>{children}</I18nContext.Provider>
<UNSAFE_AkselLanguageProvider translations={i18n}>
{children}
</UNSAFE_AkselLanguageProvider>
),
});
const translate = result.current;
Expand All @@ -29,9 +32,9 @@ describe("useI18n", () => {
const i18n2 = { FileUpload: { item: { uploading: "Wrong translation" } } };
const { result } = renderHook(() => useI18n("FileUpload"), {
wrapper: ({ children }) => (
<I18nContext.Provider value={[i18n1, i18n2]}>
<UNSAFE_AkselLanguageProvider translations={[i18n1, i18n2]}>
{children}
</I18nContext.Provider>
</UNSAFE_AkselLanguageProvider>
),
});
const translate = result.current;
Expand All @@ -45,9 +48,9 @@ describe("useI18n", () => {
};
const { result } = renderHook(() => useI18n("FileUpload"), {
wrapper: ({ children }) => (
<I18nContext.Provider value={[i18n1, i18n2]}>
<UNSAFE_AkselLanguageProvider translations={[i18n1, i18n2]}>
{children}
</I18nContext.Provider>
</UNSAFE_AkselLanguageProvider>
),
});
const translate = result.current;
Expand Down
16 changes: 5 additions & 11 deletions @navikt/core/react/src/util/i18n/i18n.context.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import { createContext, useContext } from "react";
import { useContext } from "react";
import { LanguageProviderContext } from "../../provider/i18n/LanguageProvider";
import { get } from "./get";
import {
Component,
ComponentTranslation,
TranslationDictionary,
} from "./i18n.types";
import { Component, ComponentTranslation } from "./i18n.types";
import nb from "./locales/nb";

/**
* https://regex101.com/r/LYKWi3/1
*/
const REPLACE_REGEX = /{[^}]*}/g;

export const I18nContext = createContext<
TranslationDictionary | TranslationDictionary[]
>(nb);

/* https://dev.to/pffigueiredo/typescript-utility-keyof-nested-object-2pa3 */
type NestedKeyOf<ObjectType extends object> = {
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
Expand All @@ -27,7 +20,8 @@ export function useI18n<T extends Component>(
componentName: T,
...local: (ComponentTranslation<T> | undefined)[]
) {
const i18n = useContext(I18nContext);
const languageProviderContext = useContext(LanguageProviderContext);
const i18n = languageProviderContext.translations;

/**
* https://github.com/Shopify/polaris/blob/2115f9ba2f5bcbf2ad15745233501bff2db81ecf/polaris-react/src/utilities/i18n/I18n.ts#L24
Expand Down

0 comments on commit 46a14b1

Please sign in to comment.