Skip to content

Commit 732711f

Browse files
authored
Merge branch 'main' into chore/refactor-exports
2 parents 2ec0df4 + 954d4ec commit 732711f

File tree

11 files changed

+233
-3
lines changed

11 files changed

+233
-3
lines changed

package-lock.json

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"./internal": "./internal/index.js",
1717
"./internal/metrics": "./internal/metrics.js",
1818
"./internal/testing": "./internal/testing.js",
19+
"./internal/locale": "./internal/locale.js",
1920
"./internal/analytics-metadata": "./internal/analytics-metadata/index.js",
2021
"./internal/analytics-metadata/utils": "./internal/analytics-metadata/utils.js",
2122
"./package.json": "./package.json"
@@ -40,7 +41,8 @@
4041
"prepare": "husky"
4142
},
4243
"dependencies": {
43-
"tslib": "^2.3.1"
44+
"tslib": "^2.3.1",
45+
"weekstart": "^2.0.0"
4446
},
4547
"devDependencies": {
4648
"@cloudscape-design/browser-test-tools": "^3.0.0",

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
export { default as useContainerQuery } from './container-queries/use-container-query';
55
export { default as useControllableState } from './use-controllable-state/use-controllable-state';
66

7-
export type { PropertyDescriptions } from './use-controllable-state/interfaces';
87
export type { ContainerQueryEntry } from './container-queries/interfaces';
8+
export type { PropertyDescriptions } from './use-controllable-state/interfaces';
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { mergeLocales } from '../merge-locales';
5+
6+
test('should return the first locale if it is fully specified', () => {
7+
expect(mergeLocales('en-US', 'fr-CA')).toEqual('en-US');
8+
});
9+
10+
test('should return the second locale if it extends the first', () => {
11+
expect(mergeLocales('en', 'en-US')).toEqual('en-US');
12+
});
13+
14+
test('should return the first locale if the second is different', () => {
15+
expect(mergeLocales('en', 'fr-CA')).toEqual('en');
16+
});
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { normalizeLocale } from '../normalize-locale';
5+
import setOptions from './utils/intl-polyfill';
6+
7+
function withDocumentLang(lang: string, callback: () => void) {
8+
const htmlElement = document.querySelector('html');
9+
htmlElement!.setAttribute('lang', lang);
10+
callback();
11+
htmlElement!.removeAttribute('lang');
12+
}
13+
14+
describe('normalizeLocale', () => {
15+
let consoleSpy: jest.SpyInstance;
16+
beforeEach(() => {
17+
consoleSpy = jest.spyOn(console, 'warn');
18+
setOptions({ locale: 'en-US' });
19+
});
20+
21+
afterEach(() => {
22+
expect(consoleSpy).not.toHaveBeenCalled();
23+
});
24+
25+
test('should return the provided value', () => {
26+
expect(normalizeLocale('DatePickerTest', 'en-US')).toBe('en-US');
27+
});
28+
29+
test('should extend provided value if it is short form', () => {
30+
expect(normalizeLocale('DatePickerTest', 'en')).toBe('en-US');
31+
setOptions({ locale: 'en-GB' });
32+
expect(normalizeLocale('DatePickerTest', 'en')).toBe('en-GB');
33+
});
34+
35+
test('should not extend the provided value if it starts from different language', () => {
36+
expect(normalizeLocale('DatePickerTest', 'fr')).toBe('fr');
37+
});
38+
39+
test('should replace underscores with dashes', () => {
40+
expect(normalizeLocale('DatePickerTest', 'zh_CN')).toBe('zh-CN');
41+
});
42+
43+
test('should warn if the provided value is in invalid format', () => {
44+
expect(normalizeLocale('DatePickerTest', 'not-locale')).toBe('en-US');
45+
expect(consoleSpy).toHaveBeenCalledWith(
46+
'[AwsUi] [DatePickerTest] Invalid locale provided: not-locale. Falling back to default'
47+
);
48+
consoleSpy.mockReset();
49+
});
50+
51+
test('should return document language by default', () => {
52+
withDocumentLang('en', () => {
53+
expect(normalizeLocale('DatePickerTest', null)).toBe('en-US');
54+
});
55+
});
56+
57+
test('should not extend document language with locale if they do not match', () => {
58+
withDocumentLang('fr', () => {
59+
expect(normalizeLocale('DatePickerTest', null)).toBe('fr');
60+
});
61+
});
62+
63+
test('should combine values from document lang and browser locale', () => {
64+
setOptions({ locale: 'fr-CA' });
65+
withDocumentLang('fr', () => {
66+
expect(normalizeLocale('DatePickerTest', null)).toBe('fr-CA');
67+
});
68+
});
69+
70+
test('should replace underscores with dashes in document lang', () => {
71+
withDocumentLang('zh_CN', () => {
72+
expect(normalizeLocale('DatePickerTest', null)).toBe('zh-CN');
73+
});
74+
});
75+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { normalizeStartOfWeek } from '../normalize-start-of-week';
5+
6+
// Mock the weekstart module
7+
jest.mock('weekstart', () => ({
8+
getWeekStartByLocale: jest.fn(),
9+
}));
10+
11+
import { getWeekStartByLocale } from 'weekstart';
12+
13+
const mockGetWeekStartByLocale = getWeekStartByLocale as jest.MockedFunction<typeof getWeekStartByLocale>;
14+
15+
describe('normalizeStartOfWeek', () => {
16+
beforeEach(() => {
17+
mockGetWeekStartByLocale.mockReset();
18+
});
19+
20+
describe('when startOfWeek is undefined', () => {
21+
test('should call getWeekStartByLocale with the provided locale', () => {
22+
mockGetWeekStartByLocale.mockReturnValue(0);
23+
const result = normalizeStartOfWeek(undefined, 'en-US');
24+
25+
expect(mockGetWeekStartByLocale).toHaveBeenCalledWith('en-US');
26+
expect(mockGetWeekStartByLocale).toHaveBeenCalledTimes(1);
27+
expect(result).toBe(0);
28+
});
29+
30+
test('should return Sunday (0) for US locale', () => {
31+
mockGetWeekStartByLocale.mockReturnValue(0);
32+
expect(normalizeStartOfWeek(undefined, 'en-US')).toBe(0);
33+
});
34+
35+
test('should return Monday (1) for French locale', () => {
36+
mockGetWeekStartByLocale.mockReturnValue(1);
37+
expect(normalizeStartOfWeek(undefined, 'fr-FR')).toBe(1);
38+
});
39+
});
40+
41+
test('should prioritize provided number over locale', () => {
42+
mockGetWeekStartByLocale.mockReturnValue(0);
43+
expect(normalizeStartOfWeek(1, 'en-US')).toBe(1);
44+
expect(mockGetWeekStartByLocale).not.toHaveBeenCalled();
45+
});
46+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// We use resolvedOptions to detect browser locale. This method allows us to override the value for testing.
5+
export default function setResolvedOptions(newValue: { locale: string }): void {
6+
const dateTimeFormat = new Intl.DateTimeFormat(newValue.locale);
7+
const resolvedOptions = dateTimeFormat.resolvedOptions();
8+
9+
window.Intl.DateTimeFormat.prototype.resolvedOptions = () => ({ ...resolvedOptions, ...newValue });
10+
}

src/internal/locale/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
export { mergeLocales } from './merge-locales';
5+
export { normalizeLocale } from './normalize-locale';
6+
export { DayIndex, normalizeStartOfWeek } from './normalize-start-of-week';
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
export function mergeLocales(locale: string, fullLocale: string) {
5+
const isShort = locale.length === 2;
6+
if (isShort && fullLocale.indexOf(locale) === 0) {
7+
return fullLocale;
8+
}
9+
return locale;
10+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { warnOnce } from '../logging';
5+
6+
import { mergeLocales } from './merge-locales';
7+
8+
export function normalizeLocale(component: string, locale: string | null): string {
9+
locale = checkLocale(component, locale);
10+
const browserLocale = getBrowserLocale();
11+
if (locale) {
12+
return mergeLocales(locale, browserLocale);
13+
}
14+
const htmlLocale = checkLocale(component, getHtmlElement()?.getAttribute('lang'));
15+
if (htmlLocale) {
16+
return mergeLocales(htmlLocale, browserLocale);
17+
}
18+
return browserLocale;
19+
}
20+
21+
function checkLocale(component: string, locale: string | null | undefined): string {
22+
if (!locale || locale === '') {
23+
return '';
24+
}
25+
26+
// Support underscore-delimited locales
27+
locale = locale && locale.replace(/^([a-z]{2})_/, '$1-');
28+
// Check that the value matches aa-BB pattern
29+
// TODO: support full BCP 47 spec?
30+
if (locale && !locale.match(/^[a-z]{2}(-[A-Z]{2})?$/)) {
31+
warnOnce(component, `Invalid locale provided: ${locale}. Falling back to default`);
32+
locale = '';
33+
}
34+
return locale;
35+
}
36+
37+
function getHtmlElement() {
38+
return typeof document !== 'undefined' ? document.querySelector('html') : null;
39+
}
40+
41+
function getBrowserLocale() {
42+
return new Intl.DateTimeFormat().resolvedOptions().locale;
43+
}

0 commit comments

Comments
 (0)