@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.
- package/build/components-nextjs/PageSeo/PageSeo.js +4 -15
- package/build/components-nextjs/PageSeo/PageSeo.js.map +1 -1
- package/build/components-nextjs/document.js.map +1 -1
- package/build/intl/getFallbackLocale.d.ts +8 -0
- package/build/intl/getFallbackLocale.js +21 -0
- package/build/intl/getFallbackLocale.js.map +1 -0
- package/build/intl/index.d.ts +2 -1
- package/build/intl/index.js +2 -1
- package/build/intl/index.js.map +1 -1
- package/build/intl/locale.d.ts +9 -0
- package/build/intl/locale.js.map +1 -1
- package/build/intl/localeObject.d.ts +5 -0
- package/build/intl/localeObject.js +7 -0
- package/build/intl/localeObject.js.map +1 -1
- package/build/seo/getAlternateLanguageLinks.d.ts +4 -0
- package/build/seo/getAlternateLanguageLinks.js +49 -0
- package/build/seo/getAlternateLanguageLinks.js.map +1 -0
- package/build/seo/index.d.ts +1 -0
- package/build/seo/index.js +18 -0
- package/build/seo/index.js.map +1 -0
- package/build/seo/utils/createAlternateLanguageLink.d.ts +5 -0
- package/build/seo/utils/createAlternateLanguageLink.js +15 -0
- package/build/seo/utils/createAlternateLanguageLink.js.map +1 -0
- package/build/seo/utils/getDefaultHrefLang.d.ts +11 -0
- package/build/seo/utils/getDefaultHrefLang.js +28 -0
- package/build/seo/utils/getDefaultHrefLang.js.map +1 -0
- package/build/seo/utils/mapLanguagesToLocales.d.ts +4 -0
- package/build/seo/utils/mapLanguagesToLocales.js +24 -0
- package/build/seo/utils/mapLanguagesToLocales.js.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/components-nextjs/PageSeo/PageSeo.tsx +14 -25
- package/src/components-nextjs/document.tsx +0 -1
- package/src/intl/getFallbackLocale.ts +26 -0
- package/src/intl/index.ts +2 -1
- package/src/intl/locale.ts +5 -1
- package/src/intl/localeObject.ts +8 -0
- package/src/seo/getAlternateLanguageLinks.test.ts +150 -0
- package/src/seo/getAlternateLanguageLinks.ts +72 -0
- package/src/seo/index.ts +1 -0
- package/src/seo/utils/createAlternateLanguageLink.ts +18 -0
- package/src/seo/utils/getDefaultHrefLang.ts +57 -0
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
@@ -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
package/src/intl/locale.ts
CHANGED
|
@@ -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',
|
package/src/intl/localeObject.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/seo/index.ts
ADDED
|
@@ -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
|
+
}
|