diff --git a/pages/alert/permutations.page.tsx b/pages/alert/permutations.page.tsx
index 821d8d5550..e19e2d52da 100644
--- a/pages/alert/permutations.page.tsx
+++ b/pages/alert/permutations.page.tsx
@@ -17,7 +17,7 @@ const longText =
const longTextWithLink = (
<>
Lorem ipsum dolor sit amet, consectetur adipisicing{' '}
-
+
elit
, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
diff --git a/pages/app-layout/utils/content-blocks.tsx b/pages/app-layout/utils/content-blocks.tsx
index ef1889b6b0..0385c3ab80 100644
--- a/pages/app-layout/utils/content-blocks.tsx
+++ b/pages/app-layout/utils/content-blocks.tsx
@@ -22,6 +22,7 @@ import styles from '../styles.scss';
export function Breadcrumbs() {
return (
([
{
label: 'CNAMEs',
value: (
-
+
abc.service23G24.xyz
),
diff --git a/pages/steps/permutations-utils.tsx b/pages/steps/permutations-utils.tsx
index be4bd33247..6fb25cebd6 100644
--- a/pages/steps/permutations-utils.tsx
+++ b/pages/steps/permutations-utils.tsx
@@ -316,6 +316,7 @@ export const loadingStepsInteractive: ReadonlyArray = [
Listed EC2 instances:{' '}
@@ -357,6 +358,7 @@ export const loadingSteps2Interactive: ReadonlyArray = [
Listed EC2 instances:{' '}
@@ -382,6 +384,7 @@ export const loadingSteps2Interactive: ReadonlyArray = [
Gathered Security Group IDs:{' '}
@@ -414,6 +417,7 @@ export const loadingSteps3Interactive: ReadonlyArray = [
Listed EC2 instances:{' '}
@@ -439,6 +443,7 @@ export const loadingSteps3Interactive: ReadonlyArray = [
Gathered Security Group IDs:{' '}
@@ -476,6 +481,7 @@ export const successfulStepsInteractive: ReadonlyArray = [
Listed EC2 instances:{' '}
@@ -501,6 +507,7 @@ export const successfulStepsInteractive: ReadonlyArray = [
Gathered Security Group IDs:{' '}
@@ -538,6 +545,7 @@ export const blockedStepsInteractive: ReadonlyArray = [
Listed EC2 instances:{' '}
@@ -563,6 +571,7 @@ export const blockedStepsInteractive: ReadonlyArray = [
Gathered Security Group IDs:{' '}
@@ -601,6 +610,7 @@ export const failedStepsInteractive: ReadonlyArray = [
Listed EC2 instances:{' '}
@@ -634,6 +644,7 @@ export const failedStepsWithRetryTextInteractive: ReadonlyArray
Listed EC2 instances:{' '}
@@ -672,6 +683,7 @@ export const failedStepsWithRetryButtonInteractive: ReadonlyArray
diff --git a/src/autosuggest/internal.tsx b/src/autosuggest/internal.tsx
index c824b2446e..cef41f968a 100644
--- a/src/autosuggest/internal.tsx
+++ b/src/autosuggest/internal.tsx
@@ -77,9 +77,7 @@ const InternalAutosuggest = React.forwardRef((props: InternalAutosuggestProps, r
);
const i18n = useInternalI18n('autosuggest');
- const errorIconAriaLabel = i18n('errorIconAriaLabel', restProps.errorIconAriaLabel);
const selectedAriaLabel = i18n('selectedAriaLabel', restProps.selectedAriaLabel);
- const recoveryText = i18n('recoveryText', restProps.recoveryText);
if (restProps.recoveryText && !onLoadItems) {
warnOnce('Autosuggest', '`onLoadItems` must be provided for `recoveryText` to be displayed.');
@@ -188,8 +186,8 @@ const InternalAutosuggest = React.forwardRef((props: InternalAutosuggestProps, r
...props,
isEmpty,
isFiltered,
- recoveryText,
- errorIconAriaLabel,
+ getRecoveryText: () => i18n('errorIconAriaLabel', restProps.errorIconAriaLabel),
+ getErrorIconAriaLabel: () => i18n('recoveryText', restProps.recoveryText),
onRecoveryClick: handleRecoveryClick,
filteringResultsText: filteredText,
hasRecoveryCallback: !!onLoadItems,
diff --git a/src/cards/index.tsx b/src/cards/index.tsx
index 015b80fd91..f26821c717 100644
--- a/src/cards/index.tsx
+++ b/src/cards/index.tsx
@@ -98,10 +98,12 @@ const Cards = React.forwardRef(function (
selectionType,
isItemDisabled,
onSelectionChange,
- ariaLabels: {
- itemSelectionLabel: ariaLabels?.itemSelectionLabel,
- selectionGroupLabel: i18n('ariaLabels.selectionGroupLabel', ariaLabels?.selectionGroupLabel),
- },
+ ariaLabels: selectionType
+ ? {
+ itemSelectionLabel: ariaLabels?.itemSelectionLabel,
+ selectionGroupLabel: i18n('ariaLabels.selectionGroupLabel', ariaLabels?.selectionGroupLabel),
+ }
+ : {},
});
const hasToolsHeader = header || filter || pagination || preferences;
const hasFooterPagination = isMobile && variant === 'full-page' && !!pagination;
diff --git a/src/form/internal.tsx b/src/form/internal.tsx
index f9a66c6ef3..f86c4902fe 100644
--- a/src/form/internal.tsx
+++ b/src/form/internal.tsx
@@ -35,7 +35,7 @@ export default function InternalForm({
}: InternalFormProps) {
const baseProps = getBaseProps(props);
const i18n = useInternalI18n('form');
- const errorIconAriaLabel = i18n('errorIconAriaLabel', errorIconAriaLabelOverride);
+ const errorIconAriaLabel = errorText ? i18n('errorIconAriaLabel', errorIconAriaLabelOverride) : undefined;
const analyticsComponentMetadata: GeneratedAnalyticsMetadataFormFragment = {
component: {
name: 'awsui.Form',
diff --git a/src/i18n/__tests__/i18n.test.tsx b/src/i18n/__tests__/i18n.test.tsx
index beb4a8cdda..a21748f44a 100644
--- a/src/i18n/__tests__/i18n.test.tsx
+++ b/src/i18n/__tests__/i18n.test.tsx
@@ -4,8 +4,20 @@
import React from 'react';
import { render } from '@testing-library/react';
+import { clearMessageCache } from '@cloudscape-design/component-toolkit/internal/testing';
+
import { I18nProvider, I18nProviderProps } from '../../../lib/components/i18n';
-import { MESSAGES, TestComponent } from './test-component';
+import { MESSAGES, SimpleTestComponent, TestComponent } from './test-component';
+
+beforeEach(() => {
+ clearMessageCache();
+ jest.spyOn(console, 'warn').mockImplementation(() => {});
+});
+
+afterEach(() => {
+ expect(console.warn).not.toHaveBeenCalled();
+ jest.restoreAllMocks();
+});
describe('with custom "lang" on ', () => {
afterEach(() => {
@@ -145,3 +157,10 @@ it('allows nesting providers', () => {
expect(container.querySelector('#top-level-string')).toHaveTextContent('My custom string');
expect(container.querySelector('#nested-string')).toHaveTextContent('nested string');
});
+
+it('prints a warning when a string is not provided neither via prop nor I18nProvider', () => {
+ render();
+ expect(console.warn).toHaveBeenCalledTimes(1);
+ expect(console.warn).toHaveBeenCalledWith(expect.stringMatching(/topLevelString.*I18nProvider/));
+ jest.mocked(console.warn).mockReset();
+});
diff --git a/src/i18n/__tests__/test-component.tsx b/src/i18n/__tests__/test-component.tsx
index e4248ad6c2..535c996ce6 100644
--- a/src/i18n/__tests__/test-component.tsx
+++ b/src/i18n/__tests__/test-component.tsx
@@ -60,3 +60,9 @@ export function TestComponent(props: TestComponentProps) {
);
}
+
+export function SimpleTestComponent(props: TestComponentProps) {
+ const i18n = useInternalI18n('test-component');
+
+ return {i18n('topLevelString', props.topLevelString)};
+}
diff --git a/src/i18n/context.ts b/src/i18n/context.ts
index 508429b077..9a8c7cf11a 100644
--- a/src/i18n/context.ts
+++ b/src/i18n/context.ts
@@ -3,6 +3,9 @@
import React, { useContext } from 'react';
+import { warnOnce } from '@cloudscape-design/component-toolkit/internal';
+
+import { isDevelopment } from '../internal/is-development';
import { I18nFormatArgTypes } from './messages-types';
export type CustomHandler = (formatFn: (args: FormatFnArgs) => string) => ReturnValue;
@@ -20,7 +23,15 @@ interface InternalI18nContextProps {
export const InternalI18nContext = React.createContext({
locale: null,
- format: (_namespace: string, _component: string, _key: string, provided: T) => provided,
+ format: (_namespace: string, component: string, key: string, provided: T) => {
+ if (isDevelopment && !provided) {
+ warnOnce(
+ component,
+ `Localization is not provided for key ${key}. Provide the value as a prop or use I18nProvider`
+ );
+ }
+ return provided;
+ },
});
export function useLocale(): string | null {
diff --git a/src/internal/components/dropdown-status/index.tsx b/src/internal/components/dropdown-status/index.tsx
index 3180cdb7ae..7b9002494b 100644
--- a/src/internal/components/dropdown-status/index.tsx
+++ b/src/internal/components/dropdown-status/index.tsx
@@ -12,7 +12,7 @@ import styles from './styles.css.js';
export { DropdownStatusProps };
-export interface DropdownStatusPropsExtended extends DropdownStatusProps {
+export interface DropdownStatusPropsExtended extends Omit {
isEmpty?: boolean;
isNoMatch?: boolean;
isFiltered?: boolean;
@@ -30,27 +30,16 @@ export interface DropdownStatusPropsExtended extends DropdownStatusProps {
* in case recoveryText was automatically provided by i18n.
*/
hasRecoveryCallback?: boolean;
+
+ getErrorIconAriaLabel: () => string | undefined;
+ getRecoveryText: () => string | undefined;
}
function DropdownStatus({ children }: { children: React.ReactNode }) {
return {children}
;
}
-type UseDropdownStatus = ({
- statusType,
- empty,
- loadingText,
- finishedText,
- filteringResultsText,
- errorText,
- recoveryText,
- isEmpty,
- isNoMatch,
- isFiltered,
- noMatch,
- hasRecoveryCallback,
- onRecoveryClick,
-}: DropdownStatusPropsExtended) => DropdownStatusResult;
+type UseDropdownStatus = (statusProps: DropdownStatusPropsExtended) => DropdownStatusResult;
export interface DropdownStatusResult {
isSticky: boolean;
@@ -65,14 +54,14 @@ export const useDropdownStatus: UseDropdownStatus = ({
finishedText,
filteringResultsText,
errorText,
- recoveryText,
+ getRecoveryText,
isEmpty,
isNoMatch,
isFiltered,
noMatch,
onRecoveryClick,
hasRecoveryCallback = false,
- errorIconAriaLabel,
+ getErrorIconAriaLabel,
}): DropdownStatusResult => {
const previousStatusType = usePrevious(statusType);
const statusResult: DropdownStatusResult = { isSticky: true, content: null, hasRecoveryButton: false };
@@ -80,6 +69,7 @@ export const useDropdownStatus: UseDropdownStatus = ({
if (statusType === 'loading') {
statusResult.content = {loadingText};
} else if (statusType === 'error') {
+ const recoveryText = getRecoveryText();
statusResult.hasRecoveryButton = !!recoveryText && hasRecoveryCallback;
statusResult.content = (
@@ -87,7 +77,7 @@ export const useDropdownStatus: UseDropdownStatus = ({
type="error"
__display="inline"
__animate={previousStatusType !== 'error'}
- iconAriaLabel={errorIconAriaLabel}
+ iconAriaLabel={getErrorIconAriaLabel()}
>
{errorText}
{' '}
diff --git a/src/link/internal.tsx b/src/link/internal.tsx
index cccffbab81..d659a523cc 100644
--- a/src/link/internal.tsx
+++ b/src/link/internal.tsx
@@ -180,7 +180,7 @@ const InternalLink = React.forwardRef(
sharedProps['aria-labelledby'] = `${sharedProps.id} ${infoId} ${infoLinkLabelFromContext}`;
}
- const renderedExternalIconAriaLabel = i18n('externalIconAriaLabel', externalIconAriaLabel);
+ const renderedExternalIconAriaLabel = external ? i18n('externalIconAriaLabel', externalIconAriaLabel) : undefined;
const content = (
<>
{children}
diff --git a/src/multiselect/embedded.tsx b/src/multiselect/embedded.tsx
index e827c24d0b..7e01c4b936 100644
--- a/src/multiselect/embedded.tsx
+++ b/src/multiselect/embedded.tsx
@@ -44,7 +44,6 @@ const EmbeddedMultiselect = React.forwardRef(
filteringType,
ariaLabel,
selectedOptions,
- deselectAriaLabel,
virtualScroll,
filteringText = '',
...restProps
@@ -58,8 +57,6 @@ const EmbeddedMultiselect = React.forwardRef(
options,
selectedOptions,
filteringType,
- disabled: false,
- deselectAriaLabel,
controlId: formFieldContext.controlId,
ariaLabelId,
footerId,
diff --git a/src/multiselect/internal.tsx b/src/multiselect/internal.tsx
index f2ed2cf44b..6025af7bd6 100644
--- a/src/multiselect/internal.tsx
+++ b/src/multiselect/internal.tsx
@@ -73,8 +73,6 @@ const InternalMultiselect = React.forwardRef(
options,
selectedOptions,
filteringType,
- disabled,
- deselectAriaLabel,
controlId,
ariaLabelId,
footerId,
@@ -122,9 +120,11 @@ const InternalMultiselect = React.forwardRef(
iconUrl: option.iconUrl,
iconSvg: option.iconSvg,
tags: option.tags,
- dismissLabel: i18n('deselectAriaLabel', deselectAriaLabel?.(option), format =>
- format({ option__label: option.label ?? '' })
- ),
+ dismissLabel: hideTokens
+ ? undefined
+ : i18n('deselectAriaLabel', deselectAriaLabel?.(option), format =>
+ format({ option__label: option.label ?? '' })
+ ),
}));
const ListComponent = virtualScroll ? VirtualList : PlainList;
diff --git a/src/multiselect/use-multiselect.tsx b/src/multiselect/use-multiselect.tsx
index 307c9f8849..da323549f1 100644
--- a/src/multiselect/use-multiselect.tsx
+++ b/src/multiselect/use-multiselect.tsx
@@ -30,10 +30,8 @@ type UseMultiselectOptions = SomeRequired<
| 'selectedOptions'
| 'filteringType'
| 'filteringResultsText'
- | 'disabled'
| 'noMatch'
| 'renderHighlightedAriaLive'
- | 'deselectAriaLabel'
| 'keepOpen'
| 'onBlur'
| 'onFocus'
@@ -56,7 +54,6 @@ export function useMultiselect({
options,
filteringType,
filteringResultsText,
- disabled,
statusType,
empty,
loadingText,
@@ -65,7 +62,6 @@ export function useMultiselect({
noMatch,
renderHighlightedAriaLive,
selectedOptions,
- deselectAriaLabel,
keepOpen,
onBlur,
onFocus,
@@ -82,10 +78,7 @@ export function useMultiselect({
}: UseMultiselectOptions) {
checkOptionValueField('Multiselect', 'options', options);
- const i18n = useInternalI18n('multiselect');
const i18nCommon = useInternalI18n('select');
- const recoveryText = i18nCommon('recoveryText', restProps.recoveryText);
- const errorIconAriaLabel = i18nCommon('errorIconAriaLabel', restProps.errorIconAriaLabel);
const selectedAriaLabel = i18nCommon('selectedAriaLabel', restProps.selectedAriaLabel);
if (restProps.recoveryText && !onLoadItems) {
@@ -190,14 +183,14 @@ export function useMultiselect({
loadingText,
finishedText,
errorText,
- recoveryText,
+ getRecoveryText: () => i18nCommon('recoveryText', restProps.recoveryText),
isEmpty,
isNoMatch,
noMatch,
isFiltered,
filteringResultsText: filteredText,
onRecoveryClick: handleRecoveryClick,
- errorIconAriaLabel: errorIconAriaLabel,
+ getErrorIconAriaLabel: () => i18nCommon('errorIconAriaLabel', restProps.errorIconAriaLabel),
hasRecoveryCallback: !!onLoadItems,
});
@@ -209,21 +202,6 @@ export function useMultiselect({
renderHighlightedAriaLive,
});
- const tokens: TokenGroupProps['items'] = selectedOptions.map(option => ({
- label: option.label,
- disabled: disabled || option.disabled,
- labelTag: option.labelTag,
- description: option.description,
- iconAlt: option.iconAlt,
- iconName: option.iconName,
- iconUrl: option.iconUrl,
- iconSvg: option.iconSvg,
- tags: option.tags,
- dismissLabel: i18n('deselectAriaLabel', deselectAriaLabel?.(option), format =>
- format({ option__label: option.label ?? '' })
- ),
- }));
-
useEffect(() => {
scrollToIndex.current?.(highlightedIndex);
}, [highlightedIndex]);
@@ -248,7 +226,6 @@ export function useMultiselect({
return {
isOpen,
- tokens,
announcement,
dropdownStatus,
filteringValue,
diff --git a/src/popover/internal.tsx b/src/popover/internal.tsx
index a371d5dda1..9198bfa7bc 100644
--- a/src/popover/internal.tsx
+++ b/src/popover/internal.tsx
@@ -69,7 +69,7 @@ function InternalPopover(
const clickFrameId = useRef(null);
const i18n = useInternalI18n('popover');
- const dismissAriaLabel = i18n('dismissAriaLabel', restProps.dismissAriaLabel);
+ const dismissAriaLabel = dismissButton ? i18n('dismissAriaLabel', restProps.dismissAriaLabel) : undefined;
const [visible, setVisible] = useState(false);
diff --git a/src/property-filter/property-filter-autosuggest.tsx b/src/property-filter/property-filter-autosuggest.tsx
index 46f76e925e..1a3a618f95 100644
--- a/src/property-filter/property-filter-autosuggest.tsx
+++ b/src/property-filter/property-filter-autosuggest.tsx
@@ -164,6 +164,8 @@ const PropertyFilterAutosuggest = React.forwardRef(
isEmpty,
onRecoveryClick: handleRecoveryClick,
hasRecoveryCallback: !!onLoadItems,
+ getErrorIconAriaLabel: () => undefined,
+ getRecoveryText: () => props.recoveryText,
});
let content = null;
diff --git a/src/select/internal.tsx b/src/select/internal.tsx
index 5657019db1..4ecc31c56d 100644
--- a/src/select/internal.tsx
+++ b/src/select/internal.tsx
@@ -78,9 +78,7 @@ const InternalSelect = React.forwardRef(
const formFieldContext = useFormFieldContext(restProps);
const i18n = useInternalI18n('select');
- const errorIconAriaLabel = i18n('errorIconAriaLabel', restProps.errorIconAriaLabel);
const selectedAriaLabel = i18n('selectedAriaLabel', restProps.selectedAriaLabel);
- const recoveryText = i18n('recoveryText', restProps.recoveryText);
if (restProps.recoveryText && !onLoadItems) {
warnOnce('Select', '`onLoadItems` must be provided for `recoveryText` to be displayed.');
@@ -193,13 +191,13 @@ const InternalSelect = React.forwardRef(
loadingText,
finishedText,
errorText,
- recoveryText,
+ getRecoveryText: () => i18n('recoveryText', restProps.recoveryText),
isEmpty,
isNoMatch,
noMatch,
isFiltered,
filteringResultsText: filteredText,
- errorIconAriaLabel,
+ getErrorIconAriaLabel: () => i18n('errorIconAriaLabel', restProps.errorIconAriaLabel),
onRecoveryClick: handleRecoveryClick,
hasRecoveryCallback: !!onLoadItems,
});
diff --git a/src/table/expandable-rows/expandable-rows-utils.ts b/src/table/expandable-rows/expandable-rows-utils.ts
index caead00e36..38e1072ca2 100644
--- a/src/table/expandable-rows/expandable-rows-utils.ts
+++ b/src/table/expandable-rows/expandable-rows-utils.ts
@@ -87,8 +87,12 @@ export function useExpandableTableProps({
isExpanded: expandedSet.has(item),
onExpandableItemToggle: () =>
fireNonCancelableEvent(expandableRows?.onExpandableItemToggle, { item, expanded: !expandedSet.has(item) }),
- expandButtonLabel: i18n('ariaLabels.expandButtonLabel', ariaLabels?.expandButtonLabel?.(item)),
- collapseButtonLabel: i18n('ariaLabels.collapseButtonLabel', ariaLabels?.collapseButtonLabel?.(item)),
+ expandButtonLabel: isExpandable
+ ? i18n('ariaLabels.expandButtonLabel', ariaLabels?.expandButtonLabel?.(item))
+ : undefined,
+ collapseButtonLabel: isExpandable
+ ? i18n('ariaLabels.collapseButtonLabel', ariaLabels?.collapseButtonLabel?.(item))
+ : undefined,
parent,
children,
};