@prezly/theme-kit-nextjs 1.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 (164) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +28 -0
  3. package/build/adapter-nextjs/getBasePageProps.d.ts +9 -0
  4. package/build/adapter-nextjs/getBasePageProps.js +11 -0
  5. package/build/adapter-nextjs/getBasePageProps.js.map +1 -0
  6. package/build/adapter-nextjs/index.d.ts +2 -0
  7. package/build/adapter-nextjs/index.js +3 -0
  8. package/build/adapter-nextjs/index.js.map +1 -0
  9. package/build/adapter-nextjs/processRequest.d.ts +3 -0
  10. package/build/adapter-nextjs/processRequest.js +20 -0
  11. package/build/adapter-nextjs/processRequest.js.map +1 -0
  12. package/build/data-fetching/api/PrezlyApi.d.ts +43 -0
  13. package/build/data-fetching/api/PrezlyApi.js +139 -0
  14. package/build/data-fetching/api/PrezlyApi.js.map +1 -0
  15. package/build/data-fetching/api/getPrezlyApi.d.ts +4 -0
  16. package/build/data-fetching/api/getPrezlyApi.js +17 -0
  17. package/build/data-fetching/api/getPrezlyApi.js.map +1 -0
  18. package/build/data-fetching/api/index.d.ts +2 -0
  19. package/build/data-fetching/api/index.js +3 -0
  20. package/build/data-fetching/api/index.js.map +1 -0
  21. package/build/data-fetching/api/languages.d.ts +18 -0
  22. package/build/data-fetching/api/languages.js +117 -0
  23. package/build/data-fetching/api/languages.js.map +1 -0
  24. package/build/data-fetching/api/queries.d.ts +45 -0
  25. package/build/data-fetching/api/queries.js +35 -0
  26. package/build/data-fetching/api/queries.js.map +1 -0
  27. package/build/data-fetching/constants.d.ts +2 -0
  28. package/build/data-fetching/constants.js +3 -0
  29. package/build/data-fetching/constants.js.map +1 -0
  30. package/build/data-fetching/getAssetsUrl.d.ts +1 -0
  31. package/build/data-fetching/getAssetsUrl.js +5 -0
  32. package/build/data-fetching/getAssetsUrl.js.map +1 -0
  33. package/build/data-fetching/getLocalizedCategoryData.d.ts +10 -0
  34. package/build/data-fetching/getLocalizedCategoryData.js +31 -0
  35. package/build/data-fetching/getLocalizedCategoryData.js.map +1 -0
  36. package/build/data-fetching/getNewsroomLogoUrl.d.ts +3 -0
  37. package/build/data-fetching/getNewsroomLogoUrl.js +14 -0
  38. package/build/data-fetching/getNewsroomLogoUrl.js.map +1 -0
  39. package/build/data-fetching/getPrivacyPortalUrl.d.ts +8 -0
  40. package/build/data-fetching/getPrivacyPortalUrl.js +9 -0
  41. package/build/data-fetching/getPrivacyPortalUrl.js.map +1 -0
  42. package/build/data-fetching/getStoryPublicationDate.d.ts +3 -0
  43. package/build/data-fetching/getStoryPublicationDate.jsx +10 -0
  44. package/build/data-fetching/getStoryPublicationDate.jsx.map +1 -0
  45. package/build/data-fetching/getUploadcareGroupUrl.d.ts +1 -0
  46. package/build/data-fetching/getUploadcareGroupUrl.js +5 -0
  47. package/build/data-fetching/getUploadcareGroupUrl.js.map +1 -0
  48. package/build/data-fetching/index.d.ts +9 -0
  49. package/build/data-fetching/index.js +10 -0
  50. package/build/data-fetching/index.js.map +1 -0
  51. package/build/data-fetching/lib/getAlgoliaSettings.d.ts +4 -0
  52. package/build/data-fetching/lib/getAlgoliaSettings.js +18 -0
  53. package/build/data-fetching/lib/getAlgoliaSettings.js.map +1 -0
  54. package/build/data-fetching/lib/getEnvVariables.d.ts +4 -0
  55. package/build/data-fetching/lib/getEnvVariables.js +37 -0
  56. package/build/data-fetching/lib/getEnvVariables.js.map +1 -0
  57. package/build/data-fetching/types.d.ts +11 -0
  58. package/build/data-fetching/types.js +2 -0
  59. package/build/data-fetching/types.js.map +1 -0
  60. package/build/index.d.ts +5 -0
  61. package/build/index.js +6 -0
  62. package/build/index.js.map +1 -0
  63. package/build/intl/index.d.ts +4 -0
  64. package/build/intl/index.js +5 -0
  65. package/build/intl/index.js.map +1 -0
  66. package/build/intl/lang.d.ts +12 -0
  67. package/build/intl/lang.js +25 -0
  68. package/build/intl/lang.js.map +1 -0
  69. package/build/intl/locale.d.ts +10 -0
  70. package/build/intl/locale.js +93 -0
  71. package/build/intl/locale.js.map +1 -0
  72. package/build/intl/localeConfig.d.ts +2 -0
  73. package/build/intl/localeConfig.js +113 -0
  74. package/build/intl/localeConfig.js.map +1 -0
  75. package/build/intl/localeObject.d.ts +22 -0
  76. package/build/intl/localeObject.js +59 -0
  77. package/build/intl/localeObject.js.map +1 -0
  78. package/build/newsroom-context/context.d.ts +26 -0
  79. package/build/newsroom-context/context.jsx +22 -0
  80. package/build/newsroom-context/context.jsx.map +1 -0
  81. package/build/newsroom-context/hooks/index.d.ts +11 -0
  82. package/build/newsroom-context/hooks/index.js +12 -0
  83. package/build/newsroom-context/hooks/index.js.map +1 -0
  84. package/build/newsroom-context/hooks/useAlgoliaSettings.d.ts +1 -0
  85. package/build/newsroom-context/hooks/useAlgoliaSettings.js +6 -0
  86. package/build/newsroom-context/hooks/useAlgoliaSettings.js.map +1 -0
  87. package/build/newsroom-context/hooks/useCategories.d.ts +1 -0
  88. package/build/newsroom-context/hooks/useCategories.js +6 -0
  89. package/build/newsroom-context/hooks/useCategories.js.map +1 -0
  90. package/build/newsroom-context/hooks/useCompanyInformation.d.ts +1 -0
  91. package/build/newsroom-context/hooks/useCompanyInformation.js +6 -0
  92. package/build/newsroom-context/hooks/useCompanyInformation.js.map +1 -0
  93. package/build/newsroom-context/hooks/useCurrentLocale.d.ts +1 -0
  94. package/build/newsroom-context/hooks/useCurrentLocale.js +6 -0
  95. package/build/newsroom-context/hooks/useCurrentLocale.js.map +1 -0
  96. package/build/newsroom-context/hooks/useGetLinkLocaleSlug.d.ts +2 -0
  97. package/build/newsroom-context/hooks/useGetLinkLocaleSlug.js +21 -0
  98. package/build/newsroom-context/hooks/useGetLinkLocaleSlug.js.map +1 -0
  99. package/build/newsroom-context/hooks/useGetTranslationUrl.d.ts +2 -0
  100. package/build/newsroom-context/hooks/useGetTranslationUrl.js +36 -0
  101. package/build/newsroom-context/hooks/useGetTranslationUrl.js.map +1 -0
  102. package/build/newsroom-context/hooks/useLanguages.d.ts +1 -0
  103. package/build/newsroom-context/hooks/useLanguages.js +6 -0
  104. package/build/newsroom-context/hooks/useLanguages.js.map +1 -0
  105. package/build/newsroom-context/hooks/useNewsroom.d.ts +1 -0
  106. package/build/newsroom-context/hooks/useNewsroom.js +6 -0
  107. package/build/newsroom-context/hooks/useNewsroom.js.map +1 -0
  108. package/build/newsroom-context/hooks/useNewsroomContext.d.ts +2 -0
  109. package/build/newsroom-context/hooks/useNewsroomContext.js +12 -0
  110. package/build/newsroom-context/hooks/useNewsroomContext.js.map +1 -0
  111. package/build/newsroom-context/hooks/useSelectedCategory.d.ts +1 -0
  112. package/build/newsroom-context/hooks/useSelectedCategory.js +6 -0
  113. package/build/newsroom-context/hooks/useSelectedCategory.js.map +1 -0
  114. package/build/newsroom-context/hooks/useSelectedStory.d.ts +1 -0
  115. package/build/newsroom-context/hooks/useSelectedStory.js +6 -0
  116. package/build/newsroom-context/hooks/useSelectedStory.js.map +1 -0
  117. package/build/newsroom-context/index.d.ts +2 -0
  118. package/build/newsroom-context/index.js +3 -0
  119. package/build/newsroom-context/index.js.map +1 -0
  120. package/build/types.d.ts +38 -0
  121. package/build/types.js +2 -0
  122. package/build/types.js.map +1 -0
  123. package/package.json +60 -0
  124. package/src/@types/global.d.ts +7 -0
  125. package/src/adapter-nextjs/getBasePageProps.ts +23 -0
  126. package/src/adapter-nextjs/index.ts +2 -0
  127. package/src/adapter-nextjs/processRequest.ts +31 -0
  128. package/src/data-fetching/api/PrezlyApi.ts +240 -0
  129. package/src/data-fetching/api/getPrezlyApi.ts +24 -0
  130. package/src/data-fetching/api/index.ts +2 -0
  131. package/src/data-fetching/api/languages.ts +172 -0
  132. package/src/data-fetching/api/queries.ts +44 -0
  133. package/src/data-fetching/constants.ts +2 -0
  134. package/src/data-fetching/getAssetsUrl.ts +5 -0
  135. package/src/data-fetching/getLocalizedCategoryData.ts +47 -0
  136. package/src/data-fetching/getNewsroomLogoUrl.ts +21 -0
  137. package/src/data-fetching/getPrivacyPortalUrl.ts +22 -0
  138. package/src/data-fetching/getStoryPublicationDate.tsx +15 -0
  139. package/src/data-fetching/getUploadcareGroupUrl.ts +5 -0
  140. package/src/data-fetching/index.ts +9 -0
  141. package/src/data-fetching/lib/getAlgoliaSettings.ts +29 -0
  142. package/src/data-fetching/lib/getEnvVariables.ts +44 -0
  143. package/src/data-fetching/types.ts +12 -0
  144. package/src/index.ts +5 -0
  145. package/src/intl/index.ts +4 -0
  146. package/src/intl/lang.ts +36 -0
  147. package/src/intl/locale.ts +118 -0
  148. package/src/intl/localeConfig.js +116 -0
  149. package/src/intl/localeObject.ts +73 -0
  150. package/src/newsroom-context/context.tsx +73 -0
  151. package/src/newsroom-context/hooks/index.ts +11 -0
  152. package/src/newsroom-context/hooks/useAlgoliaSettings.ts +7 -0
  153. package/src/newsroom-context/hooks/useCategories.ts +7 -0
  154. package/src/newsroom-context/hooks/useCompanyInformation.ts +7 -0
  155. package/src/newsroom-context/hooks/useCurrentLocale.ts +7 -0
  156. package/src/newsroom-context/hooks/useGetLinkLocaleSlug.ts +33 -0
  157. package/src/newsroom-context/hooks/useGetTranslationUrl.ts +61 -0
  158. package/src/newsroom-context/hooks/useLanguages.ts +7 -0
  159. package/src/newsroom-context/hooks/useNewsroom.ts +7 -0
  160. package/src/newsroom-context/hooks/useNewsroomContext.ts +14 -0
  161. package/src/newsroom-context/hooks/useSelectedCategory.ts +7 -0
  162. package/src/newsroom-context/hooks/useSelectedStory.ts +7 -0
  163. package/src/newsroom-context/index.ts +2 -0
  164. package/src/types.ts +56 -0
@@ -0,0 +1,44 @@
1
+ import type { IncomingMessage } from 'http';
2
+ import parseDataUrl from 'parse-data-url';
3
+
4
+ import type { PrezlyEnv } from '../types';
5
+
6
+ const decodeJson = (json: string): Record<string, any> => {
7
+ try {
8
+ const decoded = JSON.parse(json);
9
+ if (decoded && typeof decoded === 'object') {
10
+ return decoded;
11
+ }
12
+ } catch {
13
+ // passthru
14
+ }
15
+ return {};
16
+ };
17
+
18
+ const decodeHttpEnv = (header: string): Record<string, any> => {
19
+ if (header.startsWith('data:')) {
20
+ const parsed = parseDataUrl(header);
21
+ if (parsed && parsed.contentType === 'application/json') {
22
+ const data = parsed.toBuffer().toString('utf-8');
23
+ return decodeJson(data);
24
+ }
25
+ return {}; // unsupported data-uri
26
+ }
27
+ return decodeJson(header);
28
+ };
29
+
30
+ export const getEnvVariables = (req?: IncomingMessage): PrezlyEnv => {
31
+ if (process.browser) {
32
+ throw new Error('"getEnvVariables" should only be used on back-end side.');
33
+ }
34
+
35
+ const headerName = (process.env.HTTP_ENV_HEADER || '').toLowerCase();
36
+
37
+ if (headerName && req) {
38
+ const header = req.headers[headerName] as string | undefined;
39
+ const httpEnv = decodeHttpEnv(header || '');
40
+ return { ...process.env, ...httpEnv };
41
+ }
42
+
43
+ return { ...process.env };
44
+ };
@@ -0,0 +1,12 @@
1
+ export interface AlgoliaSettings {
2
+ ALGOLIA_API_KEY: string;
3
+ ALGOLIA_APP_ID: string;
4
+ ALGOLIA_INDEX: string;
5
+ }
6
+
7
+ export interface PrezlyNewsroomEnv {
8
+ PREZLY_ACCESS_TOKEN: string;
9
+ PREZLY_NEWSROOM_UUID: string;
10
+ }
11
+
12
+ export interface PrezlyEnv extends PrezlyNewsroomEnv, AlgoliaSettings {}
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './adapter-nextjs';
2
+ export * from './data-fetching';
3
+ export * from './intl';
4
+ export * from './newsroom-context';
5
+ export * from './types';
@@ -0,0 +1,4 @@
1
+ export * from './lang';
2
+ export * from './locale';
3
+ export { default as localeConfig } from './localeConfig';
4
+ export * from './localeObject';
@@ -0,0 +1,36 @@
1
+ import { CultureRef, NewsroomLanguageSettings } from '@prezly/sdk';
2
+
3
+ function isOnlyCulture(culture: CultureRef, languages: NewsroomLanguageSettings[]): boolean {
4
+ const numberOfLanguages = languages.filter(
5
+ ({ locale: { language_code } }) => language_code === culture.language_code,
6
+ ).length;
7
+
8
+ return numberOfLanguages === 1;
9
+ }
10
+
11
+ /**
12
+ * If there's only one culture used in a specific language,
13
+ * we strip the culture name completely.
14
+ *
15
+ * Examples:
16
+ * - English (Global), Spanish (Spain)
17
+ * -> English, Spanish
18
+ * - English (Global), English (UK), Spanish (Spain)
19
+ * -> English (Global), English (UK), Spanish
20
+ */
21
+ export function getLanguageDisplayName(
22
+ language: NewsroomLanguageSettings,
23
+ languages: NewsroomLanguageSettings[],
24
+ ): string {
25
+ const { locale } = language;
26
+
27
+ if (isOnlyCulture(locale, languages)) {
28
+ const cultureNameIndex = locale.native_name.indexOf('(');
29
+
30
+ if (cultureNameIndex !== -1) {
31
+ return locale.native_name.slice(0, cultureNameIndex - 1);
32
+ }
33
+ }
34
+
35
+ return locale.native_name;
36
+ }
@@ -0,0 +1,118 @@
1
+ import type { Redirect } from 'next';
2
+ import { ParsedUrlQuery, stringify } from 'querystring';
3
+
4
+ import { BasePageProps } from '../types';
5
+
6
+ import { LocaleObject } from './localeObject';
7
+
8
+ export const DEFAULT_LOCALE = 'en';
9
+ export const DUMMY_DEFAULT_LOCALE = 'qps-ploc';
10
+
11
+ const SUPPORTED_LOCALES = [
12
+ 'af',
13
+ 'ar',
14
+ 'az',
15
+ 'be',
16
+ 'bg',
17
+ 'cs',
18
+ 'da',
19
+ 'de',
20
+ 'el',
21
+ 'en',
22
+ 'es',
23
+ 'et',
24
+ 'fi',
25
+ 'fil',
26
+ 'fr',
27
+ 'ga',
28
+ 'he',
29
+ 'hi',
30
+ 'hr',
31
+ 'hu',
32
+ 'id',
33
+ 'it',
34
+ 'ja',
35
+ 'kk',
36
+ 'ko',
37
+ 'lt',
38
+ 'lv',
39
+ 'mt',
40
+ 'nl',
41
+ 'no',
42
+ 'pl',
43
+ 'pt-BR',
44
+ 'pt',
45
+ 'ro',
46
+ 'ru',
47
+ 'sk',
48
+ 'sl',
49
+ 'sv',
50
+ 'th',
51
+ 'tr',
52
+ 'uk',
53
+ 'ur',
54
+ 'uz',
55
+ 'vi',
56
+ 'zh-CN',
57
+ 'zh-TW',
58
+ ];
59
+
60
+ export function getSupportedLocaleIsoCode(locale: LocaleObject): string {
61
+ const localeIsoCode = locale.toHyphenCode();
62
+
63
+ const isSupportedLocale =
64
+ localeIsoCode.length >= 2 && SUPPORTED_LOCALES.includes(localeIsoCode);
65
+ if (isSupportedLocale) {
66
+ return localeIsoCode;
67
+ }
68
+
69
+ const language = localeIsoCode.slice(0, 2);
70
+ const isSupportedLanguage = SUPPORTED_LOCALES.includes(language);
71
+ if (isSupportedLanguage) {
72
+ return language;
73
+ }
74
+
75
+ return DEFAULT_LOCALE;
76
+ }
77
+
78
+ export function getRedirectToCanonicalLocale(
79
+ basePageProps: BasePageProps,
80
+ nextLocaleIsoCode: string | undefined,
81
+ redirectPath: string,
82
+ query?: ParsedUrlQuery,
83
+ ): Redirect | undefined {
84
+ const { shortestLocaleCode } = basePageProps;
85
+ const shortestLocaleSlug = shortestLocaleCode
86
+ ? LocaleObject.fromAnyCode(shortestLocaleCode).toUrlSlug()
87
+ : shortestLocaleCode;
88
+
89
+ if (nextLocaleIsoCode === DUMMY_DEFAULT_LOCALE) {
90
+ return undefined;
91
+ }
92
+
93
+ if (shortestLocaleSlug !== nextLocaleIsoCode) {
94
+ const prefixedPath =
95
+ redirectPath && !redirectPath.startsWith('/') ? `/${redirectPath}` : redirectPath;
96
+
97
+ const urlQuery = query ? `?${stringify(query)}` : '';
98
+
99
+ return {
100
+ destination: shortestLocaleSlug
101
+ ? `/${shortestLocaleSlug}${prefixedPath}${urlQuery}`
102
+ : `${prefixedPath}${urlQuery}`,
103
+ permanent: false,
104
+ };
105
+ }
106
+
107
+ return undefined;
108
+ }
109
+
110
+ export function getLocaleDirection(locale: LocaleObject): 'ltr' | 'rtl' {
111
+ const neutralLanguageCode = locale.toNeutralLanguageCode();
112
+
113
+ if (['ar', 'he', 'ur'].includes(neutralLanguageCode)) {
114
+ return 'rtl';
115
+ }
116
+
117
+ return 'ltr';
118
+ }
@@ -0,0 +1,116 @@
1
+ // This list is pulled from the main Prezly application. The underscores are replaced with dashes.
2
+ const supportedLocales = [
3
+ 'af-ZA',
4
+ 'ar',
5
+ 'ar-AE',
6
+ 'ar-BH',
7
+ 'ar-QA',
8
+ 'ar-SA',
9
+ 'az',
10
+ 'be',
11
+ 'bg-BG',
12
+ 'cs-CZ',
13
+ 'da-DK',
14
+ 'de',
15
+ 'de-DE',
16
+ 'de-AT',
17
+ 'de-CH',
18
+ 'el-GR',
19
+ 'en',
20
+ 'en-US',
21
+ 'en-AE',
22
+ 'en-AU',
23
+ 'en-BD',
24
+ 'en-BE',
25
+ 'en-BH',
26
+ 'en-CA',
27
+ 'en-CN',
28
+ 'en-DE',
29
+ 'en-DK',
30
+ 'en-GB',
31
+ 'en-HK',
32
+ 'en-ID',
33
+ 'en-IE',
34
+ 'en-IL',
35
+ 'en-IN',
36
+ 'en-JP',
37
+ 'en-KH',
38
+ 'en-KR',
39
+ 'en-LK',
40
+ 'en-MV',
41
+ 'en-MM',
42
+ 'en-MY',
43
+ 'en-NP',
44
+ 'en-NZ',
45
+ 'en-PH',
46
+ 'en-PK',
47
+ 'en-QA',
48
+ 'en-SA',
49
+ 'en-SG',
50
+ 'en-TH',
51
+ 'en-TW',
52
+ 'en-VN',
53
+ 'en-ZA',
54
+ 'es',
55
+ 'es-ES',
56
+ 'es-MX',
57
+ 'et-EE',
58
+ 'fi-FI',
59
+ 'fil',
60
+ 'fr',
61
+ 'fr-BE',
62
+ 'fr-FR',
63
+ 'fr-CA',
64
+ 'fr-LU',
65
+ 'ga-IE',
66
+ 'he-IL',
67
+ 'hi-IN',
68
+ 'hr-HR',
69
+ 'hu-HU',
70
+ 'id-ID',
71
+ 'it-IT',
72
+ 'ja-JP',
73
+ 'kk',
74
+ 'ko-KR',
75
+ 'ky',
76
+ 'lt-LT',
77
+ 'lv-LV',
78
+ 'mt-MT',
79
+ 'ms-MY',
80
+ 'nb-NO',
81
+ 'nl',
82
+ 'nl-BE',
83
+ 'nl-NL',
84
+ 'pl-PL',
85
+ 'pt',
86
+ 'pt-BR',
87
+ 'pt-PT',
88
+ 'ro-RO',
89
+ 'ru-RU',
90
+ 'sk-SK',
91
+ 'sl-SI',
92
+ 'sv-SE',
93
+ 'th-TH',
94
+ 'tr-TR',
95
+ 'uk-UA',
96
+ 'ur-PK',
97
+ 'uz',
98
+ 'vi-VN',
99
+ 'zh-Hant',
100
+ 'zh-HK',
101
+ 'zh-TW',
102
+ 'zh-CN',
103
+ ];
104
+
105
+ const lowercaseLocales = supportedLocales.map((l) => l.toLowerCase());
106
+
107
+ // Get all permutations for the shorter locale codes supported by Prezly (e.g. short region codes)
108
+ const lowercaseLocalPermutations = Array.from(
109
+ new Set([
110
+ ...lowercaseLocales,
111
+ ...lowercaseLocales.map((code) => code.split('-')[0]),
112
+ ...lowercaseLocales.map((code) => code.split('-')[1]).filter(Boolean),
113
+ ]),
114
+ ).sort();
115
+
116
+ module.exports = lowercaseLocalPermutations;
@@ -0,0 +1,73 @@
1
+ import localeConfig from './localeConfig';
2
+
3
+ export class LocaleObject {
4
+ /**
5
+ * Hyphen-separated locale code.
6
+ * Examples: `nl-BE`, `en-US`, `fr`, `fil`
7
+ */
8
+ private localeCode: string;
9
+
10
+ private constructor(localeCode: string) {
11
+ if (localeCode.indexOf('_') !== -1) {
12
+ throw new Error('Invalid locale code provided!');
13
+ }
14
+
15
+ const [language, region] = localeCode.split('-');
16
+
17
+ if (region) {
18
+ this.localeCode = [language, region.toUpperCase()].join('-');
19
+ } else {
20
+ this.localeCode = language;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * @param localeCode Locale code in any of the formats below.
26
+ * Examples:
27
+ * - Hyphen-codes: `nl-BE`, `en-US`, `fr`, `fil`
28
+ * - Underscore-codes: `nl_BE`, `en_US`, `fr`, `fil`
29
+ * - Slugs: `nl-be`, `en-us`, `fr`, `fil`
30
+ */
31
+ public static fromAnyCode(localeCode: string): LocaleObject {
32
+ const lowercaseCode = localeCode.toLowerCase().replace('_', '-');
33
+ const foundCode = localeConfig.find((code) => code === lowercaseCode);
34
+
35
+ if (!foundCode) {
36
+ throw new Error(`Unsupported locale code provided: "${lowercaseCode}"`);
37
+ }
38
+
39
+ return new LocaleObject(foundCode);
40
+ }
41
+
42
+ public toHyphenCode(): string {
43
+ return this.localeCode;
44
+ }
45
+
46
+ public toUnderscoreCode(): string {
47
+ return this.localeCode.replace('-', '_');
48
+ }
49
+
50
+ public toUrlSlug(): string {
51
+ return this.localeCode.toLowerCase();
52
+ }
53
+
54
+ public toNeutralLanguageCode(): string {
55
+ const [language] = this.localeCode.split('-');
56
+
57
+ return language;
58
+ }
59
+
60
+ public toRegionCode(): string {
61
+ const [language, region] = this.localeCode.split('-');
62
+
63
+ if (!region) {
64
+ return language.toUpperCase();
65
+ }
66
+
67
+ return region.toUpperCase();
68
+ }
69
+
70
+ public isEqual(anotherLocale: LocaleObject) {
71
+ return anotherLocale.toHyphenCode() === this.localeCode;
72
+ }
73
+ }
@@ -0,0 +1,73 @@
1
+ import type {
2
+ Category,
3
+ Newsroom,
4
+ NewsroomCompanyInformation,
5
+ NewsroomContact,
6
+ NewsroomLanguageSettings,
7
+ NewsroomThemePreset,
8
+ Story,
9
+ } from '@prezly/sdk';
10
+ import { createContext, FunctionComponent, useMemo } from 'react';
11
+
12
+ import type { AlgoliaSettings } from '../data-fetching';
13
+ import { LocaleObject } from '../intl';
14
+ import { Translations } from '../types';
15
+
16
+ export interface INewsroomContext {
17
+ newsroom: Newsroom;
18
+ companyInformation: NewsroomCompanyInformation;
19
+ categories: Category[];
20
+ contacts?: NewsroomContact[];
21
+ selectedCategory?: Category;
22
+ selectedStory?: Story;
23
+ languages: NewsroomLanguageSettings[];
24
+ locale: LocaleObject;
25
+ hasError?: boolean;
26
+ themePreset: NewsroomThemePreset;
27
+ algoliaSettings: AlgoliaSettings;
28
+ }
29
+
30
+ interface Props extends Omit<INewsroomContext, 'locale'> {
31
+ isTrackingEnabled?: boolean;
32
+ localeCode: string;
33
+ translations?: Translations;
34
+ }
35
+
36
+ export const NewsroomContext = createContext<INewsroomContext | undefined>(undefined);
37
+
38
+ export const NewsroomContextProvider: FunctionComponent<Props> = ({
39
+ categories,
40
+ contacts,
41
+ newsroom,
42
+ selectedCategory,
43
+ selectedStory,
44
+ companyInformation,
45
+ languages,
46
+ localeCode,
47
+ hasError,
48
+ themePreset,
49
+ algoliaSettings,
50
+ children,
51
+ }) => {
52
+ const locale = useMemo(() => LocaleObject.fromAnyCode(localeCode || 'en'), [localeCode]);
53
+
54
+ return (
55
+ <NewsroomContext.Provider
56
+ value={{
57
+ categories,
58
+ contacts,
59
+ newsroom,
60
+ selectedCategory,
61
+ selectedStory,
62
+ companyInformation,
63
+ languages,
64
+ locale,
65
+ hasError,
66
+ themePreset,
67
+ algoliaSettings,
68
+ }}
69
+ >
70
+ {children}
71
+ </NewsroomContext.Provider>
72
+ );
73
+ };
@@ -0,0 +1,11 @@
1
+ export * from './useAlgoliaSettings';
2
+ export * from './useCategories';
3
+ export * from './useCompanyInformation';
4
+ export * from './useCurrentLocale';
5
+ export * from './useGetLinkLocaleSlug';
6
+ export * from './useGetTranslationUrl';
7
+ export * from './useLanguages';
8
+ export * from './useNewsroom';
9
+ export * from './useNewsroomContext';
10
+ export * from './useSelectedCategory';
11
+ export * from './useSelectedStory';
@@ -0,0 +1,7 @@
1
+ import { useNewsroomContext } from './useNewsroomContext';
2
+
3
+ export const useAlgoliaSettings = () => {
4
+ const context = useNewsroomContext();
5
+
6
+ return context.algoliaSettings;
7
+ };
@@ -0,0 +1,7 @@
1
+ import { useNewsroomContext } from './useNewsroomContext';
2
+
3
+ export const useCategories = () => {
4
+ const context = useNewsroomContext();
5
+
6
+ return context.categories;
7
+ };
@@ -0,0 +1,7 @@
1
+ import { useNewsroomContext } from './useNewsroomContext';
2
+
3
+ export const useCompanyInformation = () => {
4
+ const context = useNewsroomContext();
5
+
6
+ return context.companyInformation;
7
+ };
@@ -0,0 +1,7 @@
1
+ import { useNewsroomContext } from './useNewsroomContext';
2
+
3
+ export const useCurrentLocale = () => {
4
+ const context = useNewsroomContext();
5
+
6
+ return context.locale;
7
+ };
@@ -0,0 +1,33 @@
1
+ import type { NewsroomLanguageSettings } from '@prezly/sdk';
2
+ import { useCallback } from 'react';
3
+
4
+ import { getShortestLocaleCode } from '../../data-fetching';
5
+ import { LocaleObject } from '../../intl';
6
+
7
+ import { useCurrentLocale } from './useCurrentLocale';
8
+ import { useLanguages } from './useLanguages';
9
+
10
+ // Determine correct locale slug for the link (trying to shorten the locale code if possible)
11
+ function getLinkLocaleSlug(
12
+ languages: NewsroomLanguageSettings[],
13
+ locale: LocaleObject,
14
+ ): string | false {
15
+ const shortestLocaleCode = getShortestLocaleCode(languages, locale);
16
+ // When navigating to default language, we don't append the locale to the URL.
17
+ if (!shortestLocaleCode) {
18
+ return shortestLocaleCode;
19
+ }
20
+
21
+ return LocaleObject.fromAnyCode(shortestLocaleCode).toUrlSlug();
22
+ }
23
+
24
+ export function useGetLinkLocaleSlug() {
25
+ const currentLocale = useCurrentLocale();
26
+ const languages = useLanguages();
27
+
28
+ // The `locale` parameter is mainly used for Language Dropdown
29
+ return useCallback(
30
+ (locale?: LocaleObject) => getLinkLocaleSlug(languages, locale || currentLocale),
31
+ [languages, currentLocale],
32
+ );
33
+ }
@@ -0,0 +1,61 @@
1
+ import type { Category, Story } from '@prezly/sdk';
2
+ import { useRouter } from 'next/router';
3
+ import { useCallback } from 'react';
4
+
5
+ import { getCategoryHasTranslation, getCategoryUrl } from '../../data-fetching';
6
+ import { LocaleObject } from '../../intl';
7
+
8
+ import { useSelectedCategory } from './useSelectedCategory';
9
+ import { useSelectedStory } from './useSelectedStory';
10
+
11
+ // Determine correct URL for translated stories/categories with a fallback to homepage
12
+ function getTranslationUrl(
13
+ locale: LocaleObject,
14
+ path: string,
15
+ selectedCategory?: Category,
16
+ selectedStory?: Story,
17
+ noFallback?: boolean,
18
+ ) {
19
+ if (selectedCategory) {
20
+ if (getCategoryHasTranslation(selectedCategory, locale)) {
21
+ return getCategoryUrl(selectedCategory, locale);
22
+ }
23
+
24
+ if (noFallback) {
25
+ return '';
26
+ }
27
+
28
+ return '/';
29
+ }
30
+
31
+ const localeCode = locale.toUnderscoreCode();
32
+
33
+ if (selectedStory && selectedStory.culture.locale !== localeCode) {
34
+ const translatedStory = selectedStory.translations.find(
35
+ ({ culture }) => culture.locale === localeCode,
36
+ );
37
+ if (translatedStory) {
38
+ return `/${translatedStory.slug}`;
39
+ }
40
+
41
+ if (noFallback) {
42
+ return '';
43
+ }
44
+
45
+ return '/';
46
+ }
47
+
48
+ return path;
49
+ }
50
+
51
+ export function useGetTranslationUrl() {
52
+ const { asPath } = useRouter();
53
+ const selectedCategory = useSelectedCategory();
54
+ const selectedStory = useSelectedStory();
55
+
56
+ return useCallback(
57
+ (locale: LocaleObject, noFallback?: boolean) =>
58
+ getTranslationUrl(locale, asPath, selectedCategory, selectedStory, noFallback),
59
+ [asPath, selectedCategory, selectedStory],
60
+ );
61
+ }
@@ -0,0 +1,7 @@
1
+ import { useNewsroomContext } from './useNewsroomContext';
2
+
3
+ export const useLanguages = () => {
4
+ const context = useNewsroomContext();
5
+
6
+ return context.languages;
7
+ };
@@ -0,0 +1,7 @@
1
+ import { useNewsroomContext } from './useNewsroomContext';
2
+
3
+ export const useNewsroom = () => {
4
+ const context = useNewsroomContext();
5
+
6
+ return context.newsroom;
7
+ };
@@ -0,0 +1,14 @@
1
+ import { useContext } from 'react';
2
+
3
+ import { INewsroomContext, NewsroomContext } from '../context';
4
+
5
+ export const useNewsroomContext = () => {
6
+ // TS Compiler is having a hard time figuring this type implicitly, so we have to set it explicitly
7
+ // See https://github.com/microsoft/TypeScript/issues/5711#issuecomment-340283439
8
+ const newsroomContext: INewsroomContext | undefined = useContext(NewsroomContext);
9
+ if (!newsroomContext) {
10
+ throw new Error('No `NewsroomContextProvider` found when calling `useNewsroomContext`');
11
+ }
12
+
13
+ return newsroomContext;
14
+ };
@@ -0,0 +1,7 @@
1
+ import { useNewsroomContext } from './useNewsroomContext';
2
+
3
+ export const useSelectedCategory = () => {
4
+ const context = useNewsroomContext();
5
+
6
+ return context.selectedCategory;
7
+ };
@@ -0,0 +1,7 @@
1
+ import { useNewsroomContext } from './useNewsroomContext';
2
+
3
+ export const useSelectedStory = () => {
4
+ const context = useNewsroomContext();
5
+
6
+ return context.selectedStory;
7
+ };
@@ -0,0 +1,2 @@
1
+ export { NewsroomContext, NewsroomContextProvider } from './context';
2
+ export * from './hooks';