diff --git a/README.md b/README.md index 9f4d2609..088d5c67 100644 --- a/README.md +++ b/README.md @@ -89,8 +89,14 @@ Please refer to the details on how the ngx-prefetch works [here](docs/HOW_IT_WOR If this is not the case, you can configure the full path of the resources that will be prefetched (ex: https://my-web-app.com/path/to/my-app/). It is also possible to set this value at runtime. Instead of setting it in the Builder's options, you can search for `{STATICS_FULL_PATH}` and replace it on the server side in order to inject a path. - - `localizationPattern` Pattern for the relative path of the localization file. By default, the pattern corresponds to the JSON file in a folder called localizations: `"/localizations/${language}.json"`. + - `localizationPattern` Pattern for the relative path of the localization file. By default, the pattern corresponds to the JSON file in a folder called localizations: `"localizations/${language}.json"`. If the localization pattern contains the `${language}` variable, the language value must be set (as explained [here](docs/HOW_IT_WORKS.md#localization)), and it will be replaced by the server. + + - `fallbackLocalesMap` Mapping of fallback locales (only available if there is dynamic content in the application), in case the localization file of the input language cannot be found. + First, a search for an exact match of the input language will be done in the `fallbackLocalesMap`. If not found and the input language parameter is of type locale, + a search for the shortened version of the locale (for example, search for `en` if the input language is `en-GB`) will be done. If this is not found either, a search + for the default locale `*` will be searched for. If none of these are found within the dynamic content files, the localization file will not be prefetched. + You can find a detailed example [below](README.md#example-of-fallback-locale). ### Example of full configuration @@ -111,7 +117,21 @@ Please refer to the details on how the ngx-prefetch works [here](docs/HOW_IT_WOR "crossorigin": true, "production": false, "staticsFullPath": "https://my-web-app.com/path/to/my-app/", - "localizationPattern": "/localizations/${language}.json" + "localizationPattern": "localizations/${language}.json", + "fallbackLocalesMap": { + "fr-CA": "fr-FR", + "de": "de-DE", + "*": "en-GB" + } } } ``` + +#### Example of fallback locale + +Let's assume you have the `fallbackLocalesMap` above in your configuration and three localization files in your assets: `fr-FR.json`, `de-DE.json`, and `en-GB.json`. +If your language parameter is equal to: +- `fr-FR`: you will prefetch this file directly +- `fr-CA`: you will fallback to `fr-FR` +- `de-AT`: you will fallback to `de-DE` +- `it-IT`: you will fallback to `en-GB` (the default fallback locale) diff --git a/src/index.ts b/src/index.ts index 4e9bbe0d..d7af5d65 100644 --- a/src/index.ts +++ b/src/index.ts @@ -93,7 +93,7 @@ export default createBuilder(async (options, context): Pr context.reportProgress(2, STEP_NUMBER, 'Read prefetch template file.'); const prefetchTemplate = fs.readFileSync(path.join(__dirname, 'templates', 'prefetch.mustache'), {encoding: 'utf-8'}); - const configOptions = ['crossorigin', 'resourceTypes']; + const configOptions = ['crossorigin', 'resourceTypes', 'fallbackLocalesMap']; const variables = { resourceArray: JSON.stringify(resourceArray), prefetchConfig: JSON.stringify(filterOptions(options, configOptions)), diff --git a/src/schema.json b/src/schema.json index f7605310..7c0d793d 100644 --- a/src/schema.json +++ b/src/schema.json @@ -38,7 +38,18 @@ "localizationPattern": { "type": "string", "description": "Pattern for the path of the localization file. By default, the pattern corresponds to the JSON file in a folder called localizations.", - "default": "/localizations/${language}.json" + "default": "localizations/${language}.json" + }, + "fallbackLocalesMap": { + "type": "object", + "description": "List of fallback locales mapping", + "patternProperties": { + "([a-z]{2,3})(-([A-Z]{2}|[A-Za-z]{4,5}))?|([*])": { + "type": "string", + "pattern": "([a-z]{2,3})(-([A-Z]{2}|[A-Za-z]{4,5}))?" + } + }, + "additionalProperties": false } }, "additionalProperties": false, diff --git a/src/schema.ts b/src/schema.ts index 14fca883..9a03900a 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -19,4 +19,7 @@ export interface PrefetchBuilderSchema extends JsonObject { /** Pattern for the path of the localization file. By default, the pattern corresponds to the JSON file in a folder called localizations. */ localizationPattern: string; + + /** List of fallback locales mapping */ + fallbackLocalesMap?: {[locale: string]: string}; } diff --git a/src/templates/prefetch.mustache b/src/templates/prefetch.mustache index 34860ee1..c0c01b2c 100644 --- a/src/templates/prefetch.mustache +++ b/src/templates/prefetch.mustache @@ -22,19 +22,20 @@ document.head.appendChild(res); } + function addLocalizationFile(locPattern) { + if (dynamicContentFiles.includes(locPattern)) { + resList.push(locPattern); + return true; + } + return false; + } + var resList = {{{resourceArray}}}; var staticsFullPath = '{{{staticsFullPath}}}'; var staticsFullPathKey = 'STATICS_FULL_PATH'; // for testing if (staticsFullPath === `{${staticsFullPathKey}}`) { staticsFullPath = '.'; } - var language = '{LANG}'; - var languageKey = 'LANG'; - var locPattern = `{{{localizationPattern}}}`; - if (language !== `{${languageKey}}` && /^([a-z]{2,3})(-([A-Z]{2}|[A-Za-z]{4,5}))?$/.test(language)) { - resList.push(locPattern); - } - var dynamicContentPath = '{DYNAMIC_CONTENT_PATH}'; var dynamicContentPathKey = 'DYNAMIC_CONTENT_PATH'; @@ -51,6 +52,41 @@ } } + var language = '{LANG}'; + var languageKey = 'LANG'; + if (language !== `{${languageKey}}` && /^([a-z]{2,3})(-([A-Z]{2}|[A-Za-z]{4,5}))?$/.test(language)) { + if (hasDynamicContent && dynamicContentFiles) { + if (dynamicContentFiles.includes(`{{{localizationPattern}}}`)) { + resList.push(`{{{localizationPattern}}}`); + } else if (prefetchConfig.fallbackLocalesMap) { + var fallback = false; + var inputLanguage = language; + if (prefetchConfig.fallbackLocalesMap[inputLanguage]) { + language = prefetchConfig.fallbackLocalesMap[inputLanguage]; + fallback = addLocalizationFile(`{{{localizationPattern}}}`); + } + if (!fallback && inputLanguage.includes('-')) { + language = inputLanguage.split('-')[0]; + if (prefetchConfig.fallbackLocalesMap[language]) { + language = prefetchConfig.fallbackLocalesMap[language]; + fallback = addLocalizationFile(`{{{localizationPattern}}}`); + } + } + if (!fallback && prefetchConfig.fallbackLocalesMap['*']) { + language = prefetchConfig.fallbackLocalesMap['*']; + fallback = addLocalizationFile(`{{{localizationPattern}}}`); + } + if (!fallback) { + console.warn('Language, fallback language, and default language not found in dynamic content files.') + } + } else { + console.warn('Language not found in dynamic content files.') + } + } else { + resList.push(`/{{{localizationPattern}}}`); + } + } + resList.forEach(function(resource) { if (typeof resource === 'string') { var fullPath = staticsFullPath; diff --git a/testing/basic.spec.ts b/testing/basic.spec.ts index ead88245..babe9764 100644 --- a/testing/basic.spec.ts +++ b/testing/basic.spec.ts @@ -54,7 +54,7 @@ describe('Prefetch builder', () => { crossorigin: true, production: true, staticsFullPath: '{STATICS_FULL_PATH}', - localizationPattern: '/localizations/${language}.json' + localizationPattern: 'localizations/${language}.json' }; const expectedBuilderConfig = {