From 6f2522d92f9742770b4548cf0ae59fa31496a37d Mon Sep 17 00:00:00 2001 From: Zeeshan Mehboob Date: Fri, 14 Aug 2026 22:59:22 +0530 Subject: [PATCH 1/3] #4977 Multilingual support for claims title and its value Signed-off-by: Zeeshan Mehboob --- .../react/src/components/adapters/Consent.tsx | 39 +++++++++++++++---- .../adapters/ConsentCheckboxList.tsx | 20 +++++++++- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/packages/react/src/components/adapters/Consent.tsx b/packages/react/src/components/adapters/Consent.tsx index fbe14bae..11a1f6eb 100644 --- a/packages/react/src/components/adapters/Consent.tsx +++ b/packages/react/src/components/adapters/Consent.tsx @@ -94,9 +94,12 @@ export interface ConsentProps { t?: UseTranslation['t']; } -const defaultConfig: Required> = { - essential: 'Essential Attributtes', - optional: 'Optional Attributes', +// default config for consent related translation keys +const defaultConfig: ConsentConfig = { + essential: 'essential', + optional: 'optional', + essentialInfo: 'essential_info', + optionalInfo: 'optional_info', }; /** @@ -140,14 +143,32 @@ const Consent: FC = ({ if (!text || (!t && !meta)) { return text || ''; } - return resolveFlowTemplateLiterals(text, {meta, t: t || ((k: string): string => k)}); + // first check if the key is present in the translation file, + // if not then resolve the template literals + const consentKey = `consent.${text}`; + const translated: string = t ? t(consentKey) : consentKey; + + // if the translated value is same as the consent key, + // then resolve the template literals + const resolvedValue = + translated === consentKey + ? resolveFlowTemplateLiterals(text, {meta, t: t || ((k: string): string => k)}) + : translated; + + // if the resolved value is same as the original text, + // then return empty string + return resolvedValue === text ? '' : resolvedValue; }; const config: ConsentConfig = {...defaultConfig, ...suppliedConfig}; - const essentialInfo = typeof config.essentialInfo === 'string' ? resolve(config.essentialInfo.trim()) : ''; - const optionalInfo = typeof config.optionalInfo === 'string' ? resolve(config.optionalInfo.trim()) : ''; - const essentialLabel = resolve(config['essential']); - const optionalLabel = resolve(config['optional']); + const essentialInfo = resolve(config['essentialInfo']); + const optionalInfo = resolve(config['optionalInfo']); + /** + * Falls back to default config values if essential/optional keys + * cannot be resolved via translation files or meta template literals. + */ + const essentialLabel = resolve(config['essential']) || 'Essential Attributes'; + const optionalLabel = resolve(config['optional']) || 'Optional Attributes'; /** * Method to check whether master toggle button is checked or not @@ -223,6 +244,7 @@ const Consent: FC = ({ purpose={purpose} formValues={formValues} onInputChange={onInputChange} + t={t} /> )} @@ -252,6 +274,7 @@ const Consent: FC = ({ purpose={purpose} formValues={formValues} onInputChange={onInputChange} + t={t} /> )} diff --git a/packages/react/src/components/adapters/ConsentCheckboxList.tsx b/packages/react/src/components/adapters/ConsentCheckboxList.tsx index 00d20040..922c1ae7 100644 --- a/packages/react/src/components/adapters/ConsentCheckboxList.tsx +++ b/packages/react/src/components/adapters/ConsentCheckboxList.tsx @@ -8,6 +8,7 @@ import useTheme from '../../contexts/Theme/useTheme'; import {cx} from '../../styles/emotion'; import Toggle from '../primitives/Toggle/Toggle'; import Typography from '../primitives/Typography/Typography'; +import {UseTranslation} from '../../hooks/useTranslation'; /** * Computes the form value key for tracking an optional attribute's consent state. @@ -78,6 +79,10 @@ export interface ConsentCheckboxListProps { purpose: ConsentPurposeData; /** Whether to render essential (disabled) or optional (toggleable) attributes */ variant: ConsentInputVariant; + /** + * translation data + */ + t?: UseTranslation['t']; } /** @@ -93,10 +98,21 @@ const ConsentCheckboxList: FC = ({ formValues, onInputChange, children, + t, }: ConsentCheckboxListProps) => { const {theme, colorScheme}: ReturnType = useTheme(); const styles: Record = useStyles(theme, colorScheme); + /** Resolve any remaining {{t()}} or {{meta()}} template expressions in a string at render time. */ + const resolve = (text: string | undefined): string => { + if (!text || !t) { + return text || ''; + } + + const translated: string = t(`consent.${text}`); + return translated === `consent.${text}` ? text : translated; + }; + const attributes: string[] = (variant === 'ESSENTIAL' ? purpose.essential : purpose.optional).map( (e): string => e.name, ); @@ -153,11 +169,11 @@ const ConsentCheckboxList: FC = ({ styles['typography'], )} > - {attr} + {resolve(attr)} {isEssential ? ( - Required + {resolve('required')} ) : ( Date: Mon, 17 Aug 2026 13:12:26 +0530 Subject: [PATCH 2/3] #4977 i18n added for permissions as well Signed-off-by: Zeeshan Mehboob --- .../react/src/components/adapters/Consent.tsx | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/react/src/components/adapters/Consent.tsx b/packages/react/src/components/adapters/Consent.tsx index 11a1f6eb..5ac703e5 100644 --- a/packages/react/src/components/adapters/Consent.tsx +++ b/packages/react/src/components/adapters/Consent.tsx @@ -41,8 +41,10 @@ export interface ConsentRenderProps { export interface ConsentConfig { essential?: string; optional?: string; + permission?: string; essentialInfo?: string; optionalInfo?: string; + permissionInfo?: string; } /** @@ -96,10 +98,12 @@ export interface ConsentProps { // default config for consent related translation keys const defaultConfig: ConsentConfig = { - essential: 'essential', - optional: 'optional', - essentialInfo: 'essential_info', - optionalInfo: 'optional_info', + essential: 'essential_claims', + optional: 'optional_claims', + permission: 'authorize_scope', + essentialInfo: 'essential_claims_info', + optionalInfo: 'optional_claims_info', + permissionInfo: 'authorize_scope_info', }; /** @@ -163,12 +167,14 @@ const Consent: FC = ({ const config: ConsentConfig = {...defaultConfig, ...suppliedConfig}; const essentialInfo = resolve(config['essentialInfo']); const optionalInfo = resolve(config['optionalInfo']); + const permissionInfo = resolve(config['permissionInfo']); /** * Falls back to default config values if essential/optional keys * cannot be resolved via translation files or meta template literals. */ const essentialLabel = resolve(config['essential']) || 'Essential Attributes'; const optionalLabel = resolve(config['optional']) || 'Optional Attributes'; + const permissionLabel = resolve(config['permission']) || 'Permissions'; /** * Method to check whether master toggle button is checked or not @@ -254,10 +260,11 @@ const Consent: FC = ({
- {purpose.type === 'permissions' ? 'Permissions' : optionalLabel} + {purpose.type === 'permissions' ? permissionLabel : optionalLabel} - {optionalInfo !== '' && ( - + {/* Show tooltip for optional claims/permissions according to their type */} + {Boolean(purpose.type === 'permissions' ? permissionInfo : optionalInfo) && ( + )} From 9f56dd0c8b26a281a8e5296e91d836d5ffd78fa9 Mon Sep 17 00:00:00 2001 From: Zeeshan Mehboob Date: Tue, 18 Aug 2026 13:29:25 +0530 Subject: [PATCH 3/3] #4977 updated logic according to the comments Signed-off-by: Zeeshan Mehboob --- .../react/src/components/adapters/Consent.tsx | 64 +++++-------------- .../adapters/ConsentCheckboxList.tsx | 6 +- .../presentation/auth/AuthOptionFactory.tsx | 2 - 3 files changed, 18 insertions(+), 54 deletions(-) diff --git a/packages/react/src/components/adapters/Consent.tsx b/packages/react/src/components/adapters/Consent.tsx index 5ac703e5..bf44157c 100644 --- a/packages/react/src/components/adapters/Consent.tsx +++ b/packages/react/src/components/adapters/Consent.tsx @@ -1,12 +1,7 @@ // Copyright 2026 The ThunderID Authors // SPDX-License-Identifier: Apache-2.0 -import { - type ConsentPurposeData, - FlowMetadataResponse, - PromptElement, - resolveFlowTemplateLiterals, -} from '@thunderid/browser'; +import {type ConsentPurposeData, PromptElement} from '@thunderid/browser'; import {type ChangeEvent, FC, ReactNode} from 'react'; import ConsentCheckboxList, {getConsentOptionalKey} from './ConsentCheckboxList'; import Typography from '../primitives/Typography/Typography'; @@ -80,15 +75,6 @@ export interface ConsentProps { * Callback invoked when a user toggles an optional attribute. */ onInputChange: (name: string, value: string) => void; - /** - * Config to modified detail in consent page - */ - config?: Record; - - /** - * Config of meta response - */ - meta?: FlowMetadataResponse | null; /** * translation data @@ -111,15 +97,7 @@ const defaultConfig: ConsentConfig = { * based on the data provided by the backend. It allows users to toggle optional attributes while essential * attributes are displayed as read-only. */ -const Consent: FC = ({ - consentData, - formValues, - config: suppliedConfig = {}, - onInputChange, - children, - meta, - t, -}: ConsentProps) => { +const Consent: FC = ({consentData, formValues, onInputChange, children, t}: ConsentProps) => { // Computed per render (not at module scope): a CSP nonce configured on // isn't known until the provider renders, and Emotion needs it applied before any style // insertion happens. These are static, so Emotion's own cache dedupes the repeat calls to a @@ -142,39 +120,27 @@ const Consent: FC = ({ }); const optionalSectionLabelClass: string = css({alignItems: 'center', display: 'flex', gap: '4px'}); - /** Resolve any remaining {{t()}} or {{meta()}} template expressions in a string at render time. */ + /** Resolve i18n keys */ const resolve = (text: string | undefined): string => { - if (!text || (!t && !meta)) { + if (!text || !t) { return text || ''; } - // first check if the key is present in the translation file, - // if not then resolve the template literals - const consentKey = `consent.${text}`; - const translated: string = t ? t(consentKey) : consentKey; - - // if the translated value is same as the consent key, - // then resolve the template literals - const resolvedValue = - translated === consentKey - ? resolveFlowTemplateLiterals(text, {meta, t: t || ((k: string): string => k)}) - : translated; - // if the resolved value is same as the original text, - // then return empty string - return resolvedValue === text ? '' : resolvedValue; + const key: string = `consent.${text}`; + const translated: string = t(key); + return translated === key ? '' : translated; }; - const config: ConsentConfig = {...defaultConfig, ...suppliedConfig}; - const essentialInfo = resolve(config['essentialInfo']); - const optionalInfo = resolve(config['optionalInfo']); - const permissionInfo = resolve(config['permissionInfo']); + const essentialInfo = resolve(defaultConfig['essentialInfo']); + const optionalInfo = resolve(defaultConfig['optionalInfo']); + const permissionInfo = resolve(defaultConfig['permissionInfo']); /** - * Falls back to default config values if essential/optional keys - * cannot be resolved via translation files or meta template literals. + * Falls back to default config values if essential/optional + * keys cannot be resolved via translation files . */ - const essentialLabel = resolve(config['essential']) || 'Essential Attributes'; - const optionalLabel = resolve(config['optional']) || 'Optional Attributes'; - const permissionLabel = resolve(config['permission']) || 'Permissions'; + const essentialLabel = resolve(defaultConfig['essential']) || 'Essential Attributes'; + const optionalLabel = resolve(defaultConfig['optional']) || 'Optional Attributes'; + const permissionLabel = resolve(defaultConfig['permission']) || 'Permissions'; /** * Method to check whether master toggle button is checked or not diff --git a/packages/react/src/components/adapters/ConsentCheckboxList.tsx b/packages/react/src/components/adapters/ConsentCheckboxList.tsx index 922c1ae7..1f837da8 100644 --- a/packages/react/src/components/adapters/ConsentCheckboxList.tsx +++ b/packages/react/src/components/adapters/ConsentCheckboxList.tsx @@ -103,14 +103,14 @@ const ConsentCheckboxList: FC = ({ const {theme, colorScheme}: ReturnType = useTheme(); const styles: Record = useStyles(theme, colorScheme); - /** Resolve any remaining {{t()}} or {{meta()}} template expressions in a string at render time. */ const resolve = (text: string | undefined): string => { if (!text || !t) { return text || ''; } - const translated: string = t(`consent.${text}`); - return translated === `consent.${text}` ? text : translated; + const key: string = `consent.${text}`; + const translated: string = t(key); + return translated === key ? text : translated; }; const attributes: string[] = (variant === 'ESSENTIAL' ? purpose.essential : purpose.optional).map( diff --git a/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx b/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx index e5bcacb3..6fe0b667 100644 --- a/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx +++ b/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx @@ -813,8 +813,6 @@ const createAuthComponentFromFlow = ( consentData={consentPromptRawData as any} formValues={formValues} onInputChange={onInputChange} - config={component.config} - meta={options.meta} t={options.t} /> );