From ea939db76114ed0ffb5efec452d6fcfaefe8962c Mon Sep 17 00:00:00 2001 From: Alexandre Fernandez <79476242+Alexandre-Fernandez@users.noreply.github.com> Date: Sun, 16 Oct 2022 12:08:17 +0200 Subject: [PATCH] feat: add option to show the default locale in the url (#51) - default locale can now behave as other locales and be displayed in the url - initialize astro-i18next config and to make it available at runtime refs #54 --- src/cli/generate.ts | 21 +++++++++++++++++---- src/cli/index.ts | 1 + src/cli/utils.ts | 18 ++++++++++++++---- src/config.ts | 16 ++++++++++++++++ src/constants.ts | 1 - src/index.ts | 19 +++++++++++++++---- src/types.ts | 11 +++++++++-- src/utils.ts | 44 +++++++++++--------------------------------- 8 files changed, 83 insertions(+), 48 deletions(-) create mode 100644 src/config.ts delete mode 100644 src/constants.ts diff --git a/src/cli/generate.ts b/src/cli/generate.ts index 109bddb..728acce 100644 --- a/src/cli/generate.ts +++ b/src/cli/generate.ts @@ -21,16 +21,22 @@ export const generate = ( inputPath: string, defaultLanguage: AstroI18nextConfig["defaultLanguage"], supportedLanguages: AstroI18nextConfig["supportedLanguages"], + showDefaultLocale = false, routeTranslations?: AstroI18nextConfig["routes"], outputPath: string = inputPath ): { filesToGenerate: FileToGenerate[]; timeToProcess: number } => { const start = process.hrtime(); - const astroPagesPaths = getAstroPagesPath(inputPath); + // default language page paths + const astroPagesPaths = showDefaultLocale + ? getAstroPagesPath(inputPath, defaultLanguage) + : getAstroPagesPath(inputPath); const filesToGenerate: FileToGenerate[] = []; astroPagesPaths.forEach(async function (file: string) { - const inputFilePath = [inputPath, file].join("/"); + const inputFilePath = showDefaultLocale + ? [inputPath, defaultLanguage, file].join("/") + : [inputPath, file].join("/"); const fileContents = fs.readFileSync(inputFilePath); const fileContentsString = fileContents.toString(); @@ -38,10 +44,15 @@ export const generate = ( const parsedFrontmatter = parseFrontmatter(fileContentsString); supportedLanguages.forEach((language) => { + const isOtherLanguage = language !== defaultLanguage; + const frontmatterCode = generateLocalizedFrontmatter( parsedFrontmatter, language, - language === defaultLanguage ? 0 : 1 + // If showDefaultLocale then we want to have 0 depth since 1 depth was + // already added by the defaultLanguage folder + // Else we add depth only when the language is not the default one + showDefaultLocale ? 0 : Number(isOtherLanguage) ); // get the astro file contents @@ -50,10 +61,12 @@ export const generate = ( frontmatterCode ); + const createLocaleFolder = showDefaultLocale ? true : isOtherLanguage; + filesToGenerate.push({ path: createTranslatedPath( file, - language === defaultLanguage ? undefined : language, + createLocaleFolder ? language : undefined, outputPath, routeTranslations ), diff --git a/src/cli/index.ts b/src/cli/index.ts index a08a5b8..d0a4774 100755 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -38,6 +38,7 @@ yargs(hideBin(process.argv)) pagesPath, argv.config.defaultLanguage, argv.config.supportedLanguages, + argv.config.showDefaultLocale, argv.config.routes, argv.output ); diff --git a/src/cli/utils.ts b/src/cli/utils.ts index 368a52d..15352d0 100644 --- a/src/cli/utils.ts +++ b/src/cli/utils.ts @@ -119,18 +119,28 @@ export const generateLocalizedFrontmatter = ( * (https://docs.astro.build/en/core-concepts/routing/#excluding-pages) * * @param pagesDirectoryPath + * @param childDirToCrawl will make the function crawl inside the given + * `childDirToCrawl` (doesn't take paths, only dirname). */ -export const getAstroPagesPath = (pagesDirectoryPath: string): PathsOutput => { +export const getAstroPagesPath = ( + pagesDirectoryPath: string, + childDirToCrawl = undefined as + | AstroI18nextConfig["defaultLanguage"] + | undefined +): PathsOutput => { // eslint-disable-next-line new-cap const api = new fdir() .filter( (filepath) => !isFileHidden(filepath) && filepath.endsWith(".astro") ) .exclude((dirName) => isLocale(dirName)) - .withRelativePaths() - .crawl(pagesDirectoryPath); + .withRelativePaths(); - return api.sync() as PathsOutput; + return childDirToCrawl + ? (api + .crawl(`${pagesDirectoryPath}${path.sep}${childDirToCrawl}`) + .sync() as PathsOutput) + : (api.crawl(pagesDirectoryPath).sync() as PathsOutput); }; export const createFiles = (filesToGenerate: FileToGenerate[]): void => { diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..202caa0 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,16 @@ +import { AstroI18nextConfig } from "types"; + +const astroI18nextConfig: AstroI18nextConfig = { + defaultLanguage: "cimode", + supportedLanguages: [], + routes: {}, + showDefaultLocale: false, +}; + +export const getAstroI18nextConfig = () => astroI18nextConfig; + +export const setAstroI18nextConfig = (config: AstroI18nextConfig) => { + for (const key in config) { + astroI18nextConfig[key] = config[key]; + } +}; diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index 3632938..0000000 --- a/src/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const I18NEXT_ROUTES_BUNDLE_NS = "astroI18nextConfig.routes"; diff --git a/src/index.ts b/src/index.ts index d2a785c..cc4e6a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ import { AstroIntegration } from "astro"; +import { setAstroI18nextConfig } from "./config"; import { AstroI18nextConfig, AstroI18nextOptions } from "./types"; import { moveBaseLanguageToFirstIndex, deeplyStringifyObject, getUserConfig, - createResourceBundleCallback, } from "./utils"; export default (options?: AstroI18nextOptions): AstroIntegration => { @@ -78,6 +78,7 @@ export default (options?: AstroI18nextOptions): AstroIntegration => { ]; let imports = `import i18next from "i18next";`; + let i18nextInit = `i18next`; if ( astroI18nextConfig.i18nextPlugins && @@ -87,7 +88,6 @@ export default (options?: AstroI18nextOptions): AstroIntegration => { for (const key of Object.keys(astroI18nextConfig.i18nextPlugins)) { imports += `import ${key} from "${astroI18nextConfig.i18nextPlugins[key]}";`; } - // loop through plugins to use them for (const key of Object.keys(astroI18nextConfig.i18nextPlugins)) { i18nextInit += `.use(${key.replace(/[{}]/g, "")})`; @@ -95,14 +95,25 @@ export default (options?: AstroI18nextOptions): AstroIntegration => { } i18nextInit += `.init(${deeplyStringifyObject( astroI18nextConfig.i18next - )}, ${createResourceBundleCallback(astroI18nextConfig.routes)});`; + )});`; - injectScript("page-ssr", imports + i18nextInit); + // initializing runtime astro-i18next config + imports += `import {initAstroI18next} from "astro-i18next";`; + const astroI18nextInit = `initAstroI18next(${JSON.stringify( + astroI18nextConfig + )});`; + + injectScript("page-ssr", imports + i18nextInit + astroI18nextInit); }, }, }; }; +export function initAstroI18next(config: AstroI18nextConfig) { + // init runtime config + setAstroI18nextConfig(config); +} + export { interpolate, localizePath, diff --git a/src/types.ts b/src/types.ts index 5a4c4e8..f2d7c01 100644 --- a/src/types.ts +++ b/src/types.ts @@ -48,9 +48,16 @@ export interface AstroI18nextConfig { /** * The translations for your routes. * - * @default undefined + * @default {} */ - routes: { + routes?: { [language: string]: Record; }; + + /** + * The display behaviour for the URL locale. + * + * @default false + */ + showDefaultLocale?: boolean; } diff --git a/src/utils.ts b/src/utils.ts index f557160..2ea2b32 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,9 @@ -import i18next, { type i18n, t } from "i18next"; +import i18next, { t } from "i18next"; import { fileURLToPath } from "url"; import load from "@proload/core"; import { AstroI18nextConfig } from "./types"; import typescript from "@proload/plugin-tsm"; -import { I18NEXT_ROUTES_BUNDLE_NS } from "./constants"; +import { getAstroI18nextConfig } from "./config"; /** * Adapted from astro's tailwind integration: @@ -214,38 +214,34 @@ export const localizePath = ( return base + path; } + const { routes, showDefaultLocale } = getAstroI18nextConfig(); let pathSegments = path.split("/"); if ( JSON.stringify(pathSegments) === JSON.stringify([""]) || JSON.stringify(pathSegments) === JSON.stringify(["", ""]) ) { + if (showDefaultLocale) return `${base}${locale}/`; return locale === i18next.options.supportedLngs[0] ? base : `${base}${locale}/`; } - // make a copy of i18next's supportedLngs - const otherLocales = [...(i18next.options.supportedLngs as string[])]; - otherLocales.slice(1); // remove base locale (first index) - - // loop over all locales except the base one - for (const otherLocale of otherLocales) { - if (pathSegments[0] === otherLocale) { - // if the path starts with one of the other locales, remove it from the path + // remove locale from pathSegments (if there is any) + for (const locale of i18next.options.supportedLngs as string[]) { + if (pathSegments[0] === locale) { pathSegments.shift(); - break; // no need to continue + break; } } // translating pathSegments - const routeTranslations = getLanguageRouteTranslations(i18next, locale) || {}; pathSegments = pathSegments.map((segment) => - routeTranslations[segment] ? routeTranslations[segment] : segment + routes[locale]?.[segment] ? routes[locale][segment] : segment ); - // prepend the given locale if it's not the base one - if (locale !== i18next.options.supportedLngs[0]) { + // prepend the given locale if it's not the base one (unless showDefaultLocale) + if (showDefaultLocale || locale !== i18next.options.supportedLngs[0]) { pathSegments = [locale, ...pathSegments]; } @@ -341,21 +337,3 @@ export const deeplyStringifyObject = (obj: object | Array): string => { } return `${str}${isArray ? "]" : "}"}`; }; - -export const createResourceBundleCallback = ( - routes: AstroI18nextConfig["routes"] = {} -) => { - let callback = "() => {"; - for (const lang in routes) { - callback += `i18next.addResourceBundle("${lang}", "${I18NEXT_ROUTES_BUNDLE_NS}", ${JSON.stringify( - routes[lang] - )});`; - } - return `${callback}}`; -}; - -export const getLanguageRouteTranslations = (i18next: i18n, lang: string) => { - return i18next.getResourceBundle(lang, I18NEXT_ROUTES_BUNDLE_NS) as - | AstroI18nextConfig["routes"][keyof AstroI18nextConfig["routes"]] - | undefined; -};