Skip to content

Commit

Permalink
feat: add option to show the default locale in the url (#51)
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
Alexandre-Fernandez authored and yassinedoghri committed Nov 6, 2022
1 parent ff14354 commit ea939db
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 48 deletions.
21 changes: 17 additions & 4 deletions src/cli/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,38 @@ 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();

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
Expand All @@ -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
),
Expand Down
1 change: 1 addition & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ yargs(hideBin(process.argv))
pagesPath,
argv.config.defaultLanguage,
argv.config.supportedLanguages,
argv.config.showDefaultLocale,
argv.config.routes,
argv.output
);
Expand Down
18 changes: 14 additions & 4 deletions src/cli/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
16 changes: 16 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -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];
}
};
1 change: 0 additions & 1 deletion src/constants.ts

This file was deleted.

19 changes: 15 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -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 => {
Expand Down Expand Up @@ -78,6 +78,7 @@ export default (options?: AstroI18nextOptions): AstroIntegration => {
];

let imports = `import i18next from "i18next";`;

let i18nextInit = `i18next`;
if (
astroI18nextConfig.i18nextPlugins &&
Expand All @@ -87,22 +88,32 @@ 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, "")})`;
}
}
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,
Expand Down
11 changes: 9 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,16 @@ export interface AstroI18nextConfig {
/**
* The translations for your routes.
*
* @default undefined
* @default {}
*/
routes: {
routes?: {
[language: string]: Record<string, string>;
};

/**
* The display behaviour for the URL locale.
*
* @default false
*/
showDefaultLocale?: boolean;
}
44 changes: 11 additions & 33 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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];
}

Expand Down Expand Up @@ -341,21 +337,3 @@ export const deeplyStringifyObject = (obj: object | Array<any>): 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;
};

0 comments on commit ea939db

Please sign in to comment.