@prezly/theme-kit-nextjs 3.2.2 → 3.2.3-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 (43) hide show
  1. package/build/components-nextjs/PageSeo/PageSeo.js +4 -15
  2. package/build/components-nextjs/PageSeo/PageSeo.js.map +1 -1
  3. package/build/components-nextjs/document.js.map +1 -1
  4. package/build/intl/getFallbackLocale.d.ts +8 -0
  5. package/build/intl/getFallbackLocale.js +21 -0
  6. package/build/intl/getFallbackLocale.js.map +1 -0
  7. package/build/intl/index.d.ts +2 -1
  8. package/build/intl/index.js +2 -1
  9. package/build/intl/index.js.map +1 -1
  10. package/build/intl/locale.d.ts +9 -0
  11. package/build/intl/locale.js.map +1 -1
  12. package/build/intl/localeObject.d.ts +5 -0
  13. package/build/intl/localeObject.js +7 -0
  14. package/build/intl/localeObject.js.map +1 -1
  15. package/build/seo/getAlternateLanguageLinks.d.ts +4 -0
  16. package/build/seo/getAlternateLanguageLinks.js +49 -0
  17. package/build/seo/getAlternateLanguageLinks.js.map +1 -0
  18. package/build/seo/index.d.ts +1 -0
  19. package/build/seo/index.js +18 -0
  20. package/build/seo/index.js.map +1 -0
  21. package/build/seo/utils/createAlternateLanguageLink.d.ts +5 -0
  22. package/build/seo/utils/createAlternateLanguageLink.js +15 -0
  23. package/build/seo/utils/createAlternateLanguageLink.js.map +1 -0
  24. package/build/seo/utils/getDefaultHrefLang.d.ts +11 -0
  25. package/build/seo/utils/getDefaultHrefLang.js +28 -0
  26. package/build/seo/utils/getDefaultHrefLang.js.map +1 -0
  27. package/build/seo/utils/mapLanguagesToLocales.d.ts +4 -0
  28. package/build/seo/utils/mapLanguagesToLocales.js +24 -0
  29. package/build/seo/utils/mapLanguagesToLocales.js.map +1 -0
  30. package/build/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +4 -4
  32. package/src/components-nextjs/PageSeo/PageSeo.tsx +14 -25
  33. package/src/components-nextjs/document.tsx +0 -1
  34. package/src/intl/getFallbackLocale.ts +26 -0
  35. package/src/intl/index.ts +2 -1
  36. package/src/intl/locale.ts +5 -1
  37. package/src/intl/localeObject.ts +8 -0
  38. package/src/seo/getAlternateLanguageLinks.test.ts +150 -0
  39. package/src/seo/getAlternateLanguageLinks.ts +72 -0
  40. package/src/seo/index.ts +1 -0
  41. package/src/seo/utils/createAlternateLanguageLink.ts +18 -0
  42. package/src/seo/utils/getDefaultHrefLang.ts +57 -0
  43. package/src/seo/utils/mapLanguagesToLocales.ts +28 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prezly/theme-kit-nextjs",
3
- "version": "3.2.2",
3
+ "version": "3.2.3-0",
4
4
  "description": "Data layer and utility library for developing Prezly themes with NextJS",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -56,19 +56,19 @@
56
56
  },
57
57
  "devDependencies": {
58
58
  "@prezly/eslint-config": "4.3.2",
59
- "@sentry/nextjs": "7.34.0",
59
+ "@sentry/nextjs": "7.35.0",
60
60
  "@types/jest": "29.4.0",
61
61
  "@types/node": "17.0.14",
62
62
  "@types/parse-data-url": "3.0.0",
63
63
  "@types/react": "18.0.27",
64
64
  "@types/react-dom": "18.0.10",
65
- "eslint": "8.32.0",
65
+ "eslint": "8.33.0",
66
66
  "jest": "29.4.1",
67
67
  "np": "7.6.3",
68
68
  "prettier": "2.8.3",
69
69
  "rimraf": "3.0.2",
70
70
  "ts-jest": "29.0.5",
71
- "typescript": "4.9.4"
71
+ "typescript": "4.9.5"
72
72
  },
73
73
  "publishConfig": {
74
74
  "access": "public"
@@ -3,7 +3,7 @@ import type { NextSeoProps } from 'next-seo';
3
3
  import { useRouter } from 'next/router';
4
4
  import { useMemo } from 'react';
5
5
 
6
- import { getUsedLanguages, LocaleObject } from '../../intl';
6
+ import { getUsedLanguages } from '../../intl';
7
7
  import {
8
8
  useCompanyInformation,
9
9
  useCurrentLocale,
@@ -13,6 +13,7 @@ import {
13
13
  useLanguages,
14
14
  useNewsroom,
15
15
  } from '../../newsroom-context';
16
+ import { getAlternateLanguageLinks } from '../../seo';
16
17
  import { getNewsroomLogoUrl } from '../../utils';
17
18
 
18
19
  import { getAbsoluteUrl } from './lib/getAbsoluteUrl';
@@ -65,31 +66,19 @@ export function PageSeo({
65
66
  return [];
66
67
  }
67
68
 
68
- const alternateLanguages = getUsedLanguages(languages).filter(
69
- (language) => language.code !== currentLocale.toUnderscoreCode(),
70
- );
71
-
72
- return alternateLanguages
73
- .map((language) => {
74
- const locale = LocaleObject.fromAnyCode(language.code);
75
-
76
- const translationLink = getTranslationUrl(locale, true);
69
+ const usedLanguages = getUsedLanguages(languages);
77
70
 
78
- if (!translationLink) {
79
- return undefined;
80
- }
81
-
82
- return {
83
- hrefLang: locale.toHyphenCode(),
84
- href: getAbsoluteUrl(
85
- translationLink,
86
- site.url,
87
- currentStory && translationLink !== '/' ? false : getLinkLocaleSlug(locale),
88
- ),
89
- };
90
- })
91
- .filter<AlternateLanguageLink>(Boolean as any);
92
- }, [currentLocale, getLinkLocaleSlug, getTranslationUrl, languages, site.url, currentStory]);
71
+ return getAlternateLanguageLinks(
72
+ usedLanguages,
73
+ getTranslationUrl,
74
+ (locale, translationUrl) =>
75
+ getAbsoluteUrl(
76
+ translationUrl,
77
+ site.url,
78
+ currentStory && translationUrl !== '/' ? false : getLinkLocaleSlug(locale),
79
+ ),
80
+ );
81
+ }, [currentStory, getLinkLocaleSlug, getTranslationUrl, languages, site.url]);
93
82
 
94
83
  return (
95
84
  <NextSeo
@@ -26,7 +26,6 @@ export class PrezlyThemeDocument extends Document {
26
26
  <Html lang={locale.toHyphenCode()} dir={direction}>
27
27
  <Head>
28
28
  <meta name="og:locale" content={locale.toHyphenCode()} />
29
- {/* TODO: Add alternate locales */}
30
29
  </Head>
31
30
  <body>
32
31
  <Main />
@@ -0,0 +1,26 @@
1
+ import type { LangCode } from './locale';
2
+ import { LocaleObject } from './localeObject';
3
+
4
+ /**
5
+ * Fallback locales for each language.
6
+ * For example, a story has no en(global) locale, then we can take some fallback locale from here.
7
+ */
8
+ export const localeFallback: Record<LangCode, LocaleObject[]> = {
9
+ en: [LocaleObject.fromAnyCode('en-US'), LocaleObject.fromAnyCode('en-GB')],
10
+ fr: [LocaleObject.fromAnyCode('fr-FR'), LocaleObject.fromAnyCode('fr-CA')],
11
+ };
12
+
13
+ export function getFallbackLocale(
14
+ langCode: LangCode,
15
+ availableLocales: LocaleObject[],
16
+ ): LocaleObject | undefined {
17
+ const fallbacks = localeFallback[langCode];
18
+
19
+ if (!fallbacks) {
20
+ return undefined;
21
+ }
22
+
23
+ return fallbacks.find((fallback) =>
24
+ Boolean(availableLocales.find((available) => available.isEqual(fallback))),
25
+ );
26
+ }
package/src/intl/index.ts CHANGED
@@ -1,4 +1,5 @@
1
+ export * from './getFallbackLocale';
1
2
  export * from './languages';
2
- export * from './locale';
3
3
  export { default as localeConfig } from './localeConfig';
4
+ export * from './locale';
4
5
  export * from './localeObject';
@@ -8,11 +8,15 @@ export const DEFAULT_LOCALE = 'en';
8
8
  // We use pseudo locale used for localization testing, to reliably determine if we need to fallback to the default newsroom language
9
9
  export const DUMMY_DEFAULT_LOCALE = 'qps-ploc';
10
10
 
11
+ export type LangCode = string & { __BRAND__?: 'langCode' };
12
+ export type RegionCode = string & { __BRAND__?: 'regionCode' };
13
+ export type LocaleCode = string & { __BRAND__?: 'localeCode' };
14
+
11
15
  /**
12
16
  * This a list of locales that Prezly has translations for. Each code represents a language file in the `@prezly/themes-intl-messages` package.
13
17
  * See https://github.com/prezly/themes-intl-messages for more info.
14
18
  */
15
- const SUPPORTED_LOCALES = [
19
+ const SUPPORTED_LOCALES: LocaleCode[] = [
16
20
  'af',
17
21
  'ar',
18
22
  'az',
@@ -12,6 +12,12 @@ export class LocaleObject {
12
12
  */
13
13
  private localeCode: string;
14
14
 
15
+ /**
16
+ * Whether the locale is global (without region/culture part).
17
+ * I.E. `en` is global, while `en-US` is not.
18
+ */
19
+ public isGlobal: boolean;
20
+
15
21
  private constructor(localeCode: string) {
16
22
  if (!/^([a-z]{2,3})(-[a-z\d]{1,4})?$/i.test(localeCode)) {
17
23
  throw new Error('Invalid locale code provided!');
@@ -20,11 +26,13 @@ export class LocaleObject {
20
26
  const [language, region] = localeCode.split('-');
21
27
 
22
28
  if (region) {
29
+ this.isGlobal = false;
23
30
  // Workaround for `zh-Hant` locale, which is the only locale with non-uppercase region part
24
31
  const regionSuffix = region === 'hant' ? 'Hant' : region.toUpperCase();
25
32
 
26
33
  this.localeCode = [language, regionSuffix].join('-');
27
34
  } else {
35
+ this.isGlobal = true;
28
36
  this.localeCode = language;
29
37
  }
30
38
  }
@@ -0,0 +1,150 @@
1
+ import { NewsroomLanguageSettings } from '@prezly/sdk';
2
+ import { LocaleCode, LocaleObject } from '../intl';
3
+ import { getAlternateLanguageLinks } from './getAlternateLanguageLinks';
4
+
5
+ function lang(code: LocaleCode, isDefault = false) {
6
+ return { code, is_default: isDefault } as NewsroomLanguageSettings;
7
+ }
8
+
9
+ function getTranslationUrl(locale: LocaleObject) {
10
+ return `translationUrl/${locale.toHyphenCode()}`;
11
+ }
12
+
13
+ function createHref(locale: LocaleObject, translationUrl: string) {
14
+ return `http://localhost:3000/${locale.toNeutralLanguageCode()}/${translationUrl}`;
15
+ }
16
+
17
+ describe('getAlternateLanguageLinks', () => {
18
+ it('should handle one en-GB non-default language', () => {
19
+ const links = getAlternateLanguageLinks([lang('en-GB')], getTranslationUrl, createHref);
20
+
21
+ expect(links).toMatchObject([
22
+ { href: 'http://localhost:3000/en/translationUrl/en-GB', hrefLang: 'en' },
23
+ { href: 'http://localhost:3000/en/translationUrl/en-GB', hrefLang: 'en-GB' },
24
+ { href: 'http://localhost:3000/en/translationUrl/en-GB', hrefLang: 'x-default' },
25
+ ]);
26
+ });
27
+
28
+ it('should handle one en-GB default language', () => {
29
+ const links = getAlternateLanguageLinks(
30
+ [lang('en-GB', true)],
31
+ getTranslationUrl,
32
+ createHref,
33
+ );
34
+
35
+ expect(links).toMatchObject([
36
+ { href: 'http://localhost:3000/en/translationUrl/en-GB', hrefLang: 'en' },
37
+ { href: 'http://localhost:3000/en/translationUrl/en-GB', hrefLang: 'en-GB' },
38
+ { href: 'http://localhost:3000/en/translationUrl/en-GB', hrefLang: 'x-default' },
39
+ ]);
40
+ });
41
+
42
+ it('should handle two full locales', () => {
43
+ const links = getAlternateLanguageLinks(
44
+ [lang('en-GB', true), lang('fr-FR')],
45
+ getTranslationUrl,
46
+ createHref,
47
+ );
48
+
49
+ expect(links).toMatchObject([
50
+ { href: 'http://localhost:3000/en/translationUrl/en-GB', hrefLang: 'en' },
51
+ { href: 'http://localhost:3000/en/translationUrl/en-GB', hrefLang: 'en-GB' },
52
+ { href: 'http://localhost:3000/fr/translationUrl/fr-FR', hrefLang: 'fr' },
53
+ { href: 'http://localhost:3000/fr/translationUrl/fr-FR', hrefLang: 'fr-FR' },
54
+ { href: 'http://localhost:3000/en/translationUrl/en-GB', hrefLang: 'x-default' },
55
+ ]);
56
+ });
57
+
58
+ it('should use en-GB locale as x-default even if other locale is set as default', () => {
59
+ const links = getAlternateLanguageLinks(
60
+ [lang('en-GB'), lang('fr-FR', true)],
61
+ getTranslationUrl,
62
+ createHref,
63
+ );
64
+
65
+ expect(links).toMatchObject([
66
+ { href: 'http://localhost:3000/en/translationUrl/en-GB', hrefLang: 'en' },
67
+ { href: 'http://localhost:3000/en/translationUrl/en-GB', hrefLang: 'en-GB' },
68
+ { href: 'http://localhost:3000/fr/translationUrl/fr-FR', hrefLang: 'fr' },
69
+ { href: 'http://localhost:3000/fr/translationUrl/fr-FR', hrefLang: 'fr-FR' },
70
+ { href: 'http://localhost:3000/en/translationUrl/en-GB', hrefLang: 'x-default' },
71
+ ]);
72
+ });
73
+
74
+ it('should use default locale as x-default when there is eng locale', () => {
75
+ const links = getAlternateLanguageLinks(
76
+ [lang('ru-RU'), lang('fr-FR', true)],
77
+ getTranslationUrl,
78
+ createHref,
79
+ );
80
+
81
+ expect(links).toMatchObject([
82
+ { href: 'http://localhost:3000/fr/translationUrl/fr-FR', hrefLang: 'fr' },
83
+ { href: 'http://localhost:3000/fr/translationUrl/fr-FR', hrefLang: 'fr-FR' },
84
+ { href: 'http://localhost:3000/ru/translationUrl/ru-RU', hrefLang: 'ru' },
85
+ { href: 'http://localhost:3000/ru/translationUrl/ru-RU', hrefLang: 'ru-RU' },
86
+ { href: 'http://localhost:3000/fr/translationUrl/fr-FR', hrefLang: 'x-default' },
87
+ ]);
88
+ });
89
+
90
+ it('should include provided global locale', () => {
91
+ const links = getAlternateLanguageLinks(
92
+ [lang('en-US'), lang('fr'), lang('fr-FR', true)],
93
+ getTranslationUrl,
94
+ createHref,
95
+ );
96
+
97
+ expect(links).toMatchObject([
98
+ { href: 'http://localhost:3000/en/translationUrl/en-US', hrefLang: 'en' },
99
+ { href: 'http://localhost:3000/en/translationUrl/en-US', hrefLang: 'en-US' },
100
+ { href: 'http://localhost:3000/fr/translationUrl/fr', hrefLang: 'fr' },
101
+ { href: 'http://localhost:3000/fr/translationUrl/fr-FR', hrefLang: 'fr-FR' },
102
+ { href: 'http://localhost:3000/en/translationUrl/en-US', hrefLang: 'x-default' },
103
+ ]);
104
+ });
105
+
106
+ it('should use provided en global locale as x-default', () => {
107
+ const links = getAlternateLanguageLinks(
108
+ [lang('en'), lang('en-US'), lang('fr'), lang('fr-FR')],
109
+ getTranslationUrl,
110
+ createHref,
111
+ );
112
+
113
+ expect(links).toMatchObject([
114
+ { href: 'http://localhost:3000/en/translationUrl/en', hrefLang: 'en' },
115
+ { href: 'http://localhost:3000/en/translationUrl/en-US', hrefLang: 'en-US' },
116
+ { href: 'http://localhost:3000/fr/translationUrl/fr', hrefLang: 'fr' },
117
+ { href: 'http://localhost:3000/fr/translationUrl/fr-FR', hrefLang: 'fr-FR' },
118
+ { href: 'http://localhost:3000/en/translationUrl/en', hrefLang: 'x-default' },
119
+ ]);
120
+ });
121
+
122
+ it('should fallback en link to en-US locale when there is en-GB locale', () => {
123
+ const links = getAlternateLanguageLinks(
124
+ [lang('en-GB'), lang('en-US')],
125
+ getTranslationUrl,
126
+ createHref,
127
+ );
128
+
129
+ expect(links).toMatchObject([
130
+ { href: 'http://localhost:3000/en/translationUrl/en-US', hrefLang: 'en' },
131
+ { href: 'http://localhost:3000/en/translationUrl/en-GB', hrefLang: 'en-GB' },
132
+ { href: 'http://localhost:3000/en/translationUrl/en-US', hrefLang: 'en-US' },
133
+ { href: 'http://localhost:3000/en/translationUrl/en-US', hrefLang: 'x-default' },
134
+ ]);
135
+ });
136
+
137
+ it('should should omit locales if getTranslationUrl is undefined', () => {
138
+ const links = getAlternateLanguageLinks(
139
+ [lang('en-US'), lang('fr'), lang('fr-FR', true)],
140
+ (locale) => (locale.toHyphenCode() === 'en-US' ? undefined : getTranslationUrl(locale)),
141
+ createHref,
142
+ );
143
+
144
+ expect(links).toMatchObject([
145
+ { href: 'http://localhost:3000/fr/translationUrl/fr', hrefLang: 'fr' },
146
+ { href: 'http://localhost:3000/fr/translationUrl/fr-FR', hrefLang: 'fr-FR' },
147
+ { href: 'http://localhost:3000/fr/translationUrl/fr-FR', hrefLang: 'x-default' },
148
+ ]);
149
+ });
150
+ });
@@ -0,0 +1,72 @@
1
+ import type { NewsroomLanguageSettings } from '@prezly/sdk';
2
+
3
+ import type { AlternateLanguageLink } from '../components-nextjs/PageSeo';
4
+ import type { LocaleObject } from '../intl';
5
+ import { getFallbackLocale } from '../intl';
6
+
7
+ import { createAlternateLanguageLink } from './utils/createAlternateLanguageLink';
8
+ import { getDefaultHrefLang } from './utils/getDefaultHrefLang';
9
+ import { mapLanguagesToLocales } from './utils/mapLanguagesToLocales';
10
+
11
+ export function getAlternateLanguageLinks(
12
+ languages: NewsroomLanguageSettings[],
13
+ getTranslationUrl: (locale: LocaleObject) => string | undefined,
14
+ createHref: (locale: LocaleObject, translationUrl: string) => string,
15
+ ): AlternateLanguageLink[] {
16
+ const alternateLanguageFallbackGlobalLinks: AlternateLanguageLink[] = [];
17
+ const alternateLanguageLocales: LocaleObject[] = [];
18
+ const [localesByLangCode, defaultLangLocale] = mapLanguagesToLocales(languages);
19
+
20
+ localesByLangCode.forEach((locales, globalLocaleCode) => {
21
+ let hasGlobalLocale = false;
22
+
23
+ locales.forEach((locale) => {
24
+ alternateLanguageLocales.push(locale);
25
+
26
+ if (!hasGlobalLocale && locale.isGlobal) {
27
+ hasGlobalLocale = true;
28
+ }
29
+ });
30
+
31
+ if (!hasGlobalLocale) {
32
+ const localesArray = Array.from(locales.values());
33
+ const fallback =
34
+ getFallbackLocale(globalLocaleCode, localesArray) ?? localesArray.at(0);
35
+
36
+ if (fallback) {
37
+ const fallbackLink = createAlternateLanguageLink(
38
+ fallback,
39
+ getTranslationUrl,
40
+ createHref,
41
+ );
42
+
43
+ if (fallbackLink) {
44
+ alternateLanguageFallbackGlobalLinks.push({
45
+ ...fallbackLink,
46
+ hrefLang: globalLocaleCode,
47
+ });
48
+ }
49
+ }
50
+ }
51
+ });
52
+
53
+ let languageAlternates: Array<AlternateLanguageLink | undefined> = alternateLanguageLocales.map(
54
+ (locale) => createAlternateLanguageLink(locale, getTranslationUrl, createHref),
55
+ );
56
+
57
+ languageAlternates = languageAlternates.concat(alternateLanguageFallbackGlobalLinks);
58
+
59
+ const xDefault = getDefaultHrefLang({
60
+ alternateLanguageLocales,
61
+ alternateLanguageFallbackGlobalLinks,
62
+ defaultLangLocale,
63
+ getTranslationUrl,
64
+ createHref,
65
+ });
66
+
67
+ languageAlternates.push(xDefault);
68
+
69
+ return languageAlternates
70
+ .filter((link): link is Exclude<typeof link, undefined> => Boolean(link))
71
+ .sort((a, b) => a.hrefLang.localeCompare(b.hrefLang));
72
+ }
@@ -0,0 +1 @@
1
+ export * from './getAlternateLanguageLinks';
@@ -0,0 +1,18 @@
1
+ import type { LocaleObject } from '../../intl';
2
+
3
+ export function createAlternateLanguageLink(
4
+ locale: LocaleObject,
5
+ getTranslationUrl: (locale: LocaleObject) => string | undefined,
6
+ createHref: (locale: LocaleObject, translationUrl: string) => string,
7
+ ) {
8
+ const translationUrl = getTranslationUrl(locale);
9
+
10
+ if (!translationUrl) {
11
+ return undefined;
12
+ }
13
+
14
+ return {
15
+ hrefLang: locale.toHyphenCode(),
16
+ href: createHref(locale, translationUrl),
17
+ };
18
+ }
@@ -0,0 +1,57 @@
1
+ import type { AlternateLanguageLink } from '../../components-nextjs/PageSeo';
2
+ import type { LocaleObject } from '../../intl';
3
+
4
+ import { createAlternateLanguageLink } from './createAlternateLanguageLink';
5
+
6
+ interface Options {
7
+ alternateLanguageLocales: LocaleObject[];
8
+ alternateLanguageFallbackGlobalLinks: AlternateLanguageLink[];
9
+ defaultLangLocale: LocaleObject | undefined;
10
+ getTranslationUrl: (locale: LocaleObject) => string | undefined;
11
+ createHref: (locale: LocaleObject, translationUrl: string) => string;
12
+ }
13
+
14
+ export function getDefaultHrefLang({
15
+ createHref,
16
+ defaultLangLocale,
17
+ getTranslationUrl,
18
+ alternateLanguageFallbackGlobalLinks,
19
+ alternateLanguageLocales,
20
+ }: Options): AlternateLanguageLink | undefined {
21
+ const engFallback = 'en';
22
+ const hrefLang = 'x-default';
23
+
24
+ const fallbackFromLocales = alternateLanguageLocales.find(
25
+ (alternate) => alternate.isGlobal && alternate.toNeutralLanguageCode() === engFallback,
26
+ );
27
+
28
+ if (fallbackFromLocales) {
29
+ const link = createAlternateLanguageLink(
30
+ fallbackFromLocales,
31
+ getTranslationUrl,
32
+ createHref,
33
+ );
34
+
35
+ if (link) {
36
+ return { ...link, hrefLang };
37
+ }
38
+ }
39
+
40
+ const fallbackFromGlobalLocales = alternateLanguageFallbackGlobalLinks.find(
41
+ (alternate) => alternate.hrefLang === engFallback,
42
+ );
43
+
44
+ if (fallbackFromGlobalLocales) {
45
+ return { ...fallbackFromGlobalLocales, hrefLang };
46
+ }
47
+
48
+ if (defaultLangLocale) {
49
+ const link = createAlternateLanguageLink(defaultLangLocale, getTranslationUrl, createHref);
50
+
51
+ if (link) {
52
+ return { ...link, hrefLang };
53
+ }
54
+ }
55
+
56
+ return undefined;
57
+ }
@@ -0,0 +1,28 @@
1
+ import type { NewsroomLanguageSettings } from '@prezly/sdk';
2
+
3
+ import type { LangCode, LocaleCode } from '../../intl';
4
+ import { LocaleObject } from '../../intl';
5
+
6
+ export function mapLanguagesToLocales(languages: NewsroomLanguageSettings[]) {
7
+ let defaultLangLocale: LocaleObject | undefined;
8
+ const localesByLangCode = new Map<LangCode, Map<LocaleCode, LocaleObject>>();
9
+
10
+ languages.forEach((language) => {
11
+ const languageLocale = LocaleObject.fromAnyCode(language.code);
12
+ const globalLanguageCode = languageLocale.toNeutralLanguageCode();
13
+ let currentLocale = localesByLangCode.get(globalLanguageCode);
14
+
15
+ if (!currentLocale) {
16
+ currentLocale = new Map([[languageLocale.toHyphenCode(), languageLocale]]);
17
+ localesByLangCode.set(globalLanguageCode, currentLocale);
18
+ }
19
+
20
+ currentLocale.set(languageLocale.toHyphenCode(), languageLocale);
21
+
22
+ if (language.is_default) {
23
+ defaultLangLocale = languageLocale;
24
+ }
25
+ });
26
+
27
+ return [localesByLangCode, defaultLangLocale] as const;
28
+ }