diff --git a/src/cli/generate.ts b/src/cli/generate.ts index e75efea..109bddb 100644 --- a/src/cli/generate.ts +++ b/src/cli/generate.ts @@ -1,4 +1,5 @@ import fs from "fs"; +import { AstroI18nextConfig } from "types"; import { getAstroPagesPath, createFiles, @@ -6,6 +7,7 @@ import { generateLocalizedFrontmatter, overwriteAstroFrontmatter, parseFrontmatter, + createTranslatedPath, } from "./utils"; /** @@ -17,8 +19,9 @@ import { */ export const generate = ( inputPath: string, - defaultLanguage: string, - supportedLanguages: string[], + defaultLanguage: AstroI18nextConfig["defaultLanguage"], + supportedLanguages: AstroI18nextConfig["supportedLanguages"], + routeTranslations?: AstroI18nextConfig["routes"], outputPath: string = inputPath ): { filesToGenerate: FileToGenerate[]; timeToProcess: number } => { const start = process.hrtime(); @@ -48,9 +51,12 @@ export const generate = ( ); filesToGenerate.push({ - path: [outputPath, language === defaultLanguage ? null : language, file] - .filter(Boolean) - .join("/"), + path: createTranslatedPath( + file, + language === defaultLanguage ? undefined : language, + outputPath, + routeTranslations + ), source: newFileContents, }); }); diff --git a/src/cli/index.ts b/src/cli/index.ts index bde9032..a08a5b8 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.routes, argv.output ); diff --git a/src/cli/utils.ts b/src/cli/utils.ts index eb29c9d..368a52d 100644 --- a/src/cli/utils.ts +++ b/src/cli/utils.ts @@ -6,6 +6,7 @@ import path from "path"; import fs from "fs"; import ts from "typescript"; import { transformer } from "./transformer"; +import { AstroI18nextConfig } from "types"; export interface FileToGenerate { path: string; @@ -139,4 +140,28 @@ export const createFiles = (filesToGenerate: FileToGenerate[]): void => { fs.writeFileSync(fileToGenerate.path, fileToGenerate.source); }); }; + +/** + * Translates `path` with `routeTranslations` if `lang` exists in + * `routeTranslations`, else returns `[basePath, path].join("/")` or + * `[basePath, language, path].join("/")`. + * @param basePath defaults to `""`. + */ +export const createTranslatedPath = ( + path: string, + language?: string, + basePath = "", + routeTranslations: AstroI18nextConfig["routes"] = {} +) => { + if (!language) return `${basePath}/${path}`; + if (!routeTranslations[language]) return `${basePath}/${language}/${path}`; + return `${basePath}/${language}/${path + .split("/") + .map((segment) => { + const translated = routeTranslations[language][segment]; + if (!translated) return segment; + return translated; + }) + .join("/")}`; +}; /* c8 ignore stop */ diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..3632938 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1 @@ +export const I18NEXT_ROUTES_BUNDLE_NS = "astroI18nextConfig.routes"; diff --git a/src/index.ts b/src/index.ts index 9a81de7..d2a785c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import { moveBaseLanguageToFirstIndex, deeplyStringifyObject, getUserConfig, + createResourceBundleCallback, } from "./utils"; export default (options?: AstroI18nextOptions): AstroIntegration => { @@ -94,7 +95,7 @@ export default (options?: AstroI18nextOptions): AstroIntegration => { } i18nextInit += `.init(${deeplyStringifyObject( astroI18nextConfig.i18next - )});`; + )}, ${createResourceBundleCallback(astroI18nextConfig.routes)});`; injectScript("page-ssr", imports + i18nextInit); }, diff --git a/src/types.ts b/src/types.ts index 9defac9..5a4c4e8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -44,4 +44,13 @@ export interface AstroI18nextConfig { i18nextPlugins?: { [key: string]: string; }; + + /** + * The translations for your routes. + * + * @default undefined + */ + routes: { + [language: string]: Record; + }; } diff --git a/src/utils.ts b/src/utils.ts index f9cda7b..595d6a8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,8 +1,9 @@ -import i18next, { t } from "i18next"; +import i18next, { type i18n, 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"; /** * Adapted from astro's tailwind integration: @@ -150,6 +151,12 @@ export const localizePath = ( } } + // translating pathSegments + const routeTranslations = getLanguageRouteTranslations(i18next, locale) || {}; + pathSegments = pathSegments.map((segment) => + routeTranslations[segment] ? routeTranslations[segment] : segment + ); + // prepend the given locale if it's not the base one if (locale !== i18next.options.supportedLngs[0]) { pathSegments = [locale, ...pathSegments]; @@ -247,3 +254,21 @@ 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; +};