@prezly/theme-kit-nextjs 1.11.2 → 1.12.0-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/build/adapter-nextjs/index.js +5 -1
  2. package/build/adapter-nextjs/index.js.map +1 -1
  3. package/build/adapter-nextjs/page-props/index.js +5 -1
  4. package/build/adapter-nextjs/page-props/index.js.map +1 -1
  5. package/build/adapter-nextjs/page-props/sitemap/index.js +5 -1
  6. package/build/adapter-nextjs/page-props/sitemap/index.js.map +1 -1
  7. package/build/components-nextjs/PageSeo/PageSeo.js +1 -1
  8. package/build/components-nextjs/PageSeo/PageSeo.js.map +1 -1
  9. package/build/components-nextjs/PageSeo/index.js +5 -1
  10. package/build/components-nextjs/PageSeo/index.js.map +1 -1
  11. package/build/components-nextjs/StorySeo/StorySeo.js +1 -1
  12. package/build/components-nextjs/StorySeo/StorySeo.js.map +1 -1
  13. package/build/data-fetching/api/PrezlyApi.js +4 -1
  14. package/build/data-fetching/api/PrezlyApi.js.map +1 -1
  15. package/build/data-fetching/api/index.js +5 -1
  16. package/build/data-fetching/api/index.js.map +1 -1
  17. package/build/data-fetching/index.js +5 -1
  18. package/build/data-fetching/index.js.map +1 -1
  19. package/build/index.js +5 -1
  20. package/build/index.js.map +1 -1
  21. package/build/infinite-loading/index.js +5 -1
  22. package/build/infinite-loading/index.js.map +1 -1
  23. package/build/intl/index.js +5 -1
  24. package/build/intl/index.js.map +1 -1
  25. package/build/intl/languages.d.ts +2 -0
  26. package/build/intl/languages.js +8 -13
  27. package/build/intl/languages.js.map +1 -1
  28. package/build/intl/locale.js +5 -1
  29. package/build/intl/locale.js.map +1 -1
  30. package/build/intl/localeList.js +1 -0
  31. package/build/intl/localeList.js.map +1 -1
  32. package/build/intl/localeObject.js +1 -1
  33. package/build/intl/localeObject.js.map +1 -1
  34. package/build/newsroom-context/context.js +1 -1
  35. package/build/newsroom-context/context.js.map +1 -1
  36. package/build/newsroom-context/hooks/index.js +5 -1
  37. package/build/newsroom-context/hooks/index.js.map +1 -1
  38. package/build/newsroom-context/hooks/useGetLinkLocaleSlug.d.ts +1 -1
  39. package/build/newsroom-context/hooks/useGetTranslationUrl.d.ts +1 -1
  40. package/build/newsroom-context/index.js +5 -1
  41. package/build/newsroom-context/index.js.map +1 -1
  42. package/build/tsconfig.tsbuildinfo +1 -1
  43. package/build/utils/index.js +5 -1
  44. package/build/utils/index.js.map +1 -1
  45. package/package.json +15 -15
  46. package/src/__mocks__/languages.ts +365 -0
  47. package/src/__mocks__/story.ts +350 -0
  48. package/src/data-fetching/api/PrezlyApi.ts +4 -1
  49. package/src/intl/languages.test.ts +217 -0
  50. package/src/intl/languages.ts +8 -14
  51. package/src/intl/locale.test.ts +94 -0
  52. package/src/intl/locale.ts +7 -1
  53. package/src/intl/localeList.js +1 -0
  54. package/src/intl/localeObject.test.ts +78 -0
  55. package/src/intl/localeObject.ts +1 -1
@@ -71,6 +71,7 @@ export function getLanguageByNeutralLocaleCode(
71
71
 
72
72
  // Prefer default language
73
73
  const defaultLanguage = getDefaultLanguage(languages);
74
+ // TODO: This doesn't align with other methods logic
74
75
  if (defaultLanguage.code === locale.toUnderscoreCode()) {
75
76
  return defaultLanguage;
76
77
  }
@@ -79,8 +80,7 @@ export function getLanguageByNeutralLocaleCode(
79
80
  const usedLanguages = getUsedLanguages(languages);
80
81
  const usedLanguage = usedLanguages.find(
81
82
  ({ code }) =>
82
- LocaleObject.fromAnyCode(code).toNeutralLanguageCode() === neutralLanguageCode ||
83
- code === neutralLanguageCode,
83
+ LocaleObject.fromAnyCode(code).toNeutralLanguageCode() === neutralLanguageCode,
84
84
  );
85
85
  if (usedLanguage) {
86
86
  return usedLanguage;
@@ -89,8 +89,7 @@ export function getLanguageByNeutralLocaleCode(
89
89
  // Search in all languages
90
90
  return languages.find(
91
91
  ({ code }) =>
92
- LocaleObject.fromAnyCode(code).toNeutralLanguageCode() === neutralLanguageCode ||
93
- code === neutralLanguageCode,
92
+ LocaleObject.fromAnyCode(code).toNeutralLanguageCode() === neutralLanguageCode,
94
93
  );
95
94
  }
96
95
 
@@ -144,9 +143,6 @@ export function getLanguageFromNextLocaleIsoCode(
144
143
  getLanguageByExactLocaleCode(languages, locale) ||
145
144
  getLanguageByNeutralLocaleCode(languages, locale) ||
146
145
  getLanguageByShortRegionCode(languages, locale);
147
- if (targetLanguage) {
148
- return targetLanguage;
149
- }
150
146
  } else {
151
147
  targetLanguage = getLanguageByExactLocaleCode(languages, locale);
152
148
  }
@@ -175,6 +171,8 @@ export function getCompanyInformation(languages: NewsroomLanguageSettings[], loc
175
171
  * First: try shorting to neutral language code (there should be no locales with the same language code)
176
172
  * Then: try shorting to region code (there should be no locales with the same region code)
177
173
  * Finally: return the original locale code (shorting is not possible)
174
+ *
175
+ * @param locale A LocaleObject constructed from FULL locale code (taken straight from the selected language)
178
176
  */
179
177
  export function getShortestLocaleCode(
180
178
  languages: NewsroomLanguageSettings[],
@@ -189,11 +187,6 @@ export function getShortestLocaleCode(
189
187
 
190
188
  // Try shorting to neutral language code
191
189
  const neutralLanguageCode = locale.toNeutralLanguageCode();
192
- // The code is already as short as possible
193
- if (neutralLanguageCode === localeCode) {
194
- return localeCode;
195
- }
196
-
197
190
  const matchingLanguagesByNeutralCode = languages.filter(
198
191
  ({ code }) =>
199
192
  LocaleObject.fromAnyCode(code).toNeutralLanguageCode() === neutralLanguageCode ||
@@ -209,10 +202,11 @@ export function getShortestLocaleCode(
209
202
  ({ code }) => LocaleObject.fromAnyCode(code).toRegionCode() === shortRegionCode,
210
203
  );
211
204
  // Prevent collision with neutral language codes
205
+ const lowerCasedShortRegionCode = shortRegionCode.toLowerCase();
212
206
  const mathchingNeutralLanguagesByRegionCode = languages.filter(
213
207
  ({ code }) =>
214
- LocaleObject.fromAnyCode(code).toNeutralLanguageCode() === shortRegionCode ||
215
- code === shortRegionCode,
208
+ LocaleObject.fromAnyCode(code).toNeutralLanguageCode() === lowerCasedShortRegionCode ||
209
+ code === lowerCasedShortRegionCode,
216
210
  );
217
211
  if (
218
212
  matchingLanguagesByRegionCode.length === 1 &&
@@ -0,0 +1,94 @@
1
+ import {
2
+ DEFAULT_LOCALE,
3
+ DUMMY_DEFAULT_LOCALE,
4
+ getLocaleDirection,
5
+ getRedirectToCanonicalLocale,
6
+ getSupportedLocaleIsoCode,
7
+ } from './locale';
8
+ import { LocaleObject } from './localeObject';
9
+
10
+ describe('getSupportedLocaleIsoCode', () => {
11
+ it('returns a code by exact match', () => {
12
+ expect(getSupportedLocaleIsoCode(LocaleObject.fromAnyCode('en'))).toBe('en');
13
+ expect(getSupportedLocaleIsoCode(LocaleObject.fromAnyCode('af'))).toBe('af');
14
+ expect(getSupportedLocaleIsoCode(LocaleObject.fromAnyCode('pt-BR'))).toBe('pt-BR');
15
+ });
16
+
17
+ it('returns a code by neutral language code match', () => {
18
+ expect(getSupportedLocaleIsoCode(LocaleObject.fromAnyCode('en-US'))).toBe('en');
19
+ expect(getSupportedLocaleIsoCode(LocaleObject.fromAnyCode('de-DE'))).toBe('de');
20
+ expect(getSupportedLocaleIsoCode(LocaleObject.fromAnyCode('uk-UA'))).toBe('uk');
21
+ });
22
+
23
+ it('returns custom mapped codes for Chinese locales', () => {
24
+ expect(getSupportedLocaleIsoCode(LocaleObject.fromAnyCode('zh-Hant'))).toBe('zh-TW');
25
+ expect(getSupportedLocaleIsoCode(LocaleObject.fromAnyCode('zh-HK'))).toBe('zh-CN');
26
+ });
27
+
28
+ it('returns default locale code for unsupported languages', () => {
29
+ const consoleSpy = jest.spyOn(console, 'warn');
30
+
31
+ // @ts-expect-error
32
+ expect(getSupportedLocaleIsoCode(new LocaleObject('aa'))).toBe(DEFAULT_LOCALE);
33
+ expect(consoleSpy).toHaveBeenCalledWith(
34
+ 'Unsupported locale provided. Please use `LocaleObject.fromAnyCode` to ensure you are using a correct language code',
35
+ );
36
+ });
37
+ });
38
+
39
+ describe('getRedirectToCanonicalLocale', () => {
40
+ it('returns undefined when no locale or dummy locale is provided', () => {
41
+ expect(getRedirectToCanonicalLocale('en', undefined, '/')).toBe(undefined);
42
+ expect(getRedirectToCanonicalLocale('en', DUMMY_DEFAULT_LOCALE, '/')).toBe(undefined);
43
+ });
44
+
45
+ it('returns undefined when locale is already canonical', () => {
46
+ expect(getRedirectToCanonicalLocale('en', 'en', '/')).toBe(undefined);
47
+ expect(getRedirectToCanonicalLocale('nl_BE', 'nl-be', '/')).toBe(undefined);
48
+ });
49
+
50
+ it('returns a redirect object when requested locale is not canonical', () => {
51
+ expect(getRedirectToCanonicalLocale(false, 'en-us', '/test')).toEqual({
52
+ destination: '/test',
53
+ permanent: false,
54
+ });
55
+
56
+ expect(getRedirectToCanonicalLocale('en', 'en-us', '/test')).toEqual({
57
+ destination: '/en/test',
58
+ permanent: false,
59
+ });
60
+
61
+ expect(getRedirectToCanonicalLocale('en', 'en-us', 'test')).toEqual({
62
+ destination: '/en/test',
63
+ permanent: false,
64
+ });
65
+ });
66
+
67
+ it('preserves query parameters', () => {
68
+ expect(
69
+ getRedirectToCanonicalLocale('en', 'en-us', '/test', {
70
+ page: '1',
71
+ tags: ['story', 'media'],
72
+ }),
73
+ ).toEqual({
74
+ destination: '/en/test?page=1&tags=story&tags=media',
75
+ permanent: false,
76
+ });
77
+ });
78
+ });
79
+
80
+ describe('getLocaleDirection', () => {
81
+ it('returns `rtl` for right-to-left languages', () => {
82
+ const RTL_LANGUAGES = ['ar', 'ar-AE', 'ar-BH', 'ar-QA', 'ar-SA', 'he-IL', 'ur-PK'];
83
+ RTL_LANGUAGES.forEach((language) => {
84
+ expect(getLocaleDirection(LocaleObject.fromAnyCode(language))).toBe('rtl');
85
+ });
86
+ });
87
+
88
+ it('returns `ltr` for other languages', () => {
89
+ const LTR_LANGUAGES = ['en', 'en-US', 'fr-FR', 'fil', 'pt-BR', 'nl', 'nl-BE'];
90
+ LTR_LANGUAGES.forEach((language) => {
91
+ expect(getLocaleDirection(LocaleObject.fromAnyCode(language))).toBe('ltr');
92
+ });
93
+ });
94
+ });
@@ -51,6 +51,7 @@ const SUPPORTED_LOCALES = [
51
51
  'sk',
52
52
  'sl',
53
53
  'sv',
54
+ 'sw',
54
55
  'th',
55
56
  'tr',
56
57
  'uk',
@@ -74,7 +75,7 @@ export function getSupportedLocaleIsoCode(locale: LocaleObject): string {
74
75
  return localeIsoCode;
75
76
  }
76
77
 
77
- const language = localeIsoCode.slice(0, 2);
78
+ const language = locale.toNeutralLanguageCode();
78
79
  const isSupportedLanguage = SUPPORTED_LOCALES.includes(language);
79
80
  if (isSupportedLanguage) {
80
81
  return language;
@@ -88,6 +89,11 @@ export function getSupportedLocaleIsoCode(locale: LocaleObject): string {
88
89
  return 'zh-CN';
89
90
  }
90
91
 
92
+ // This code should never be reached, because locale code check is happening in `LocaleObject.fromAnyCode`
93
+ // eslint-disable-next-line no-console
94
+ console.warn(
95
+ 'Unsupported locale provided. Please use `LocaleObject.fromAnyCode` to ensure you are using a correct language code',
96
+ );
91
97
  return DEFAULT_LOCALE;
92
98
  }
93
99
 
@@ -95,6 +95,7 @@ module.exports = [
95
95
  'sk-SK',
96
96
  'sl-SI',
97
97
  'sv-SE',
98
+ 'sw',
98
99
  'th-TH',
99
100
  'tr-TR',
100
101
  'uk-UA',
@@ -0,0 +1,78 @@
1
+ import { LocaleObject } from './localeObject';
2
+
3
+ it('throws when created from invalid code', () => {
4
+ // @ts-expect-error
5
+ expect(() => new LocaleObject('en_US')).toThrow('Invalid locale code provided!');
6
+ // @ts-expect-error
7
+ expect(() => new LocaleObject('1234')).toThrow('Invalid locale code provided!');
8
+ // @ts-expect-error
9
+ expect(() => new LocaleObject('*!en-US')).toThrow('Invalid locale code provided!');
10
+ });
11
+
12
+ it('throws when created with unsupported code', () => {
13
+ const unsupportedCode = 'unknown-code';
14
+ expect(() => LocaleObject.fromAnyCode(unsupportedCode)).toThrow(
15
+ `Unsupported locale code provided: "${unsupportedCode}"`,
16
+ );
17
+ });
18
+
19
+ it('accepts any valid code type and converts it to hyphen code consistently', () => {
20
+ expect(LocaleObject.fromAnyCode('en').toHyphenCode()).toBe('en');
21
+ expect(LocaleObject.fromAnyCode('en-us').toHyphenCode()).toBe('en-US');
22
+ expect(LocaleObject.fromAnyCode('en-US').toHyphenCode()).toBe('en-US');
23
+ expect(LocaleObject.fromAnyCode('EN-US').toHyphenCode()).toBe('en-US');
24
+ expect(LocaleObject.fromAnyCode('en_us').toHyphenCode()).toBe('en-US');
25
+ expect(LocaleObject.fromAnyCode('en_US').toHyphenCode()).toBe('en-US');
26
+ expect(LocaleObject.fromAnyCode('EN_US').toHyphenCode()).toBe('en-US');
27
+ });
28
+
29
+ it('converts to underscore code consistently', () => {
30
+ expect(LocaleObject.fromAnyCode('en').toUnderscoreCode()).toBe('en');
31
+ expect(LocaleObject.fromAnyCode('en-us').toUnderscoreCode()).toBe('en_US');
32
+ expect(LocaleObject.fromAnyCode('en-US').toUnderscoreCode()).toBe('en_US');
33
+ expect(LocaleObject.fromAnyCode('EN-US').toUnderscoreCode()).toBe('en_US');
34
+ expect(LocaleObject.fromAnyCode('en_us').toUnderscoreCode()).toBe('en_US');
35
+ expect(LocaleObject.fromAnyCode('en_US').toUnderscoreCode()).toBe('en_US');
36
+ expect(LocaleObject.fromAnyCode('EN_US').toUnderscoreCode()).toBe('en_US');
37
+ });
38
+
39
+ it('converts to URL slug consistently', () => {
40
+ expect(LocaleObject.fromAnyCode('en').toUrlSlug()).toBe('en');
41
+ expect(LocaleObject.fromAnyCode('en-us').toUrlSlug()).toBe('en-us');
42
+ expect(LocaleObject.fromAnyCode('en-US').toUrlSlug()).toBe('en-us');
43
+ expect(LocaleObject.fromAnyCode('EN-US').toUrlSlug()).toBe('en-us');
44
+ expect(LocaleObject.fromAnyCode('en_us').toUrlSlug()).toBe('en-us');
45
+ expect(LocaleObject.fromAnyCode('en_US').toUrlSlug()).toBe('en-us');
46
+ expect(LocaleObject.fromAnyCode('EN_US').toUrlSlug()).toBe('en-us');
47
+ });
48
+
49
+ it('converts to neutral language code consistently', () => {
50
+ expect(LocaleObject.fromAnyCode('en').toNeutralLanguageCode()).toBe('en');
51
+ expect(LocaleObject.fromAnyCode('en-us').toNeutralLanguageCode()).toBe('en');
52
+ expect(LocaleObject.fromAnyCode('en-US').toNeutralLanguageCode()).toBe('en');
53
+ expect(LocaleObject.fromAnyCode('EN-US').toNeutralLanguageCode()).toBe('en');
54
+ expect(LocaleObject.fromAnyCode('en_us').toNeutralLanguageCode()).toBe('en');
55
+ expect(LocaleObject.fromAnyCode('en_US').toNeutralLanguageCode()).toBe('en');
56
+ expect(LocaleObject.fromAnyCode('EN_US').toNeutralLanguageCode()).toBe('en');
57
+ });
58
+
59
+ it('converts to region code consistently', () => {
60
+ expect(LocaleObject.fromAnyCode('en').toRegionCode()).toBe('EN');
61
+ expect(LocaleObject.fromAnyCode('en-us').toRegionCode()).toBe('US');
62
+ expect(LocaleObject.fromAnyCode('en-US').toRegionCode()).toBe('US');
63
+ expect(LocaleObject.fromAnyCode('EN-US').toRegionCode()).toBe('US');
64
+ expect(LocaleObject.fromAnyCode('en_us').toRegionCode()).toBe('US');
65
+ expect(LocaleObject.fromAnyCode('en_US').toRegionCode()).toBe('US');
66
+ expect(LocaleObject.fromAnyCode('EN_US').toRegionCode()).toBe('US');
67
+ });
68
+
69
+ it('can compare two objects', () => {
70
+ const enUsLocale = LocaleObject.fromAnyCode('en-US');
71
+ const enUsLocaleDuplicate = LocaleObject.fromAnyCode('en-US');
72
+ const deDeLocale = LocaleObject.fromAnyCode('de-DE');
73
+
74
+ expect(enUsLocale.isEqual(enUsLocaleDuplicate)).toBe(true);
75
+ expect(enUsLocaleDuplicate.isEqual(enUsLocale)).toBe(true);
76
+ expect(enUsLocale.isEqual(deDeLocale)).toBe(false);
77
+ expect(deDeLocale.isEqual(enUsLocale)).toBe(false);
78
+ });
@@ -13,7 +13,7 @@ export class LocaleObject {
13
13
  private localeCode: string;
14
14
 
15
15
  private constructor(localeCode: string) {
16
- if (localeCode.indexOf('_') !== -1) {
16
+ if (!/^([a-zA-Z]+)-?([a-zA-Z\d]+)$/.test(localeCode)) {
17
17
  throw new Error('Invalid locale code provided!');
18
18
  }
19
19