diff --git a/src/main/resources/assets/admin/common/js/form2/LocaleContext.tsx b/src/main/resources/assets/admin/common/js/form2/LocaleContext.tsx new file mode 100644 index 000000000..947114e2f --- /dev/null +++ b/src/main/resources/assets/admin/common/js/form2/LocaleContext.tsx @@ -0,0 +1,19 @@ +import type {ReactNode} from 'react'; +import {createContext, useContext} from 'react'; + +const LocaleContext = createContext(undefined); + +export type LocaleProviderProps = { + locale: string | undefined; + children?: ReactNode; +}; + +const LOCALE_PROVIDER_NAME = 'LocaleProvider'; + +export const LocaleProvider = ({locale, children}: LocaleProviderProps): ReactNode => { + return {children}; +}; + +LocaleProvider.displayName = LOCALE_PROVIDER_NAME; + +export const useLocale = (): string | undefined => useContext(LocaleContext); diff --git a/src/main/resources/assets/admin/common/js/form2/components/text-area-input/TextAreaInput.stories.tsx b/src/main/resources/assets/admin/common/js/form2/components/text-area-input/TextAreaInput.stories.tsx index dff927e6b..fab7cb912 100644 --- a/src/main/resources/assets/admin/common/js/form2/components/text-area-input/TextAreaInput.stories.tsx +++ b/src/main/resources/assets/admin/common/js/form2/components/text-area-input/TextAreaInput.stories.tsx @@ -9,6 +9,7 @@ import {OccurrencesBuilder} from '../../../form/Occurrences'; import type {TextAreaConfig} from '../../descriptor'; import {FieldRegistry, generateProcessingToken, type ProcessingToken} from '../../FieldRegistry'; import {FieldRegistryProvider} from '../../FieldRegistryContext'; +import {LocaleProvider} from '../../LocaleContext'; import type {InputTypeComponentProps} from '../../types'; import {TextAreaInput, type TextAreaInputProps} from './TextAreaInput'; @@ -193,6 +194,40 @@ export const Highlight: Story = { render: () => , }; +export const WithLocale: Story = { + name: 'Features / Locale (lang)', + render: () => ( + +
+
+ Wrapped in LocaleProvider locale="nb-NO". The rendered textarea gets{' '} + lang="nb" and spellcheck="true" so the browser uses the Norwegian + dictionary. +
+ +
+
+ ), +}; + +export const WithRtlLocale: Story = { + name: 'Features / Locale (RTL)', + render: () => ( + +
+
+ Wrapped in LocaleProvider locale="ar-SA". The textarea renders right-to-left with{' '} + lang="ar" dir="rtl". +
+ +
+
+ ), +}; + const STORY_PATH = '.story.field'; const OCCURRENCE_ID = 'occurrence-0'; diff --git a/src/main/resources/assets/admin/common/js/form2/components/text-area-input/TextAreaInput.tsx b/src/main/resources/assets/admin/common/js/form2/components/text-area-input/TextAreaInput.tsx index cfbda0da8..8cfbdb811 100644 --- a/src/main/resources/assets/admin/common/js/form2/components/text-area-input/TextAreaInput.tsx +++ b/src/main/resources/assets/admin/common/js/form2/components/text-area-input/TextAreaInput.tsx @@ -4,8 +4,9 @@ import {useEffect, useRef} from 'react'; import {ValueTypes} from '../../../data/ValueTypes'; import type {TextAreaConfig} from '../../descriptor'; +import {useLocale} from '../../LocaleContext'; import type {InputTypeComponentProps} from '../../types'; -import {getFirstError, getInputAccessibleName} from '../../utils'; +import {getFirstError, getInputAccessibleName, getLangAttributes} from '../../utils'; import {Counter} from '../counter'; export type TextAreaInputProps = InputTypeComponentProps; @@ -31,6 +32,8 @@ export const TextAreaInput = ({ // ? Scroll is owned by the parent InputField (gated on RevealOptions.scroll); // the inner blink should highlight only, never scroll again. const isBlinking = useBlinkAttention(textAreaRef, highlight, {scrollIntoView: false}); + const locale = useLocale(); + const langAttrs = getLangAttributes(locale); useEffect(() => { if (externalInputRef == null) return undefined; @@ -63,6 +66,7 @@ export const TextAreaInput = ({ return (