Programming

Translate routes in Astro for content collections and subpages

Artur Sopelnik
#astro#i18n#routing

In addition to the official route translation documentation I wrote, I would like to explain the steps for translating routes for content collections and subpages.

See the example below

// ui.ts

export const routes = {
  de: {
    services: "leistungen",
    blog: "blog", // content collection index
    "blog.how-to-become-a-scrum-master": "blog.wie-werde-ich-scrum-master",
  },
  fr: {
    services: "prestations-de-service",
    blog: "blog", // content collection index
    "blog.how-to-become-a-scrum-master": "blog.comment-devenir-un-scrum-master",
  },
};

Multi-tier routes are separated with a dot. When translating the route, the dot is replaced with a slash. Update the methods below to enable translation logic for multi-tier routes.

// utils.ts

// Add dot/slash replacement for translatedPath
export function useTranslatedPath(lang: keyof typeof ui) {
  return function translatePath(path: string, l: string = lang) {
    const pathName = path.replaceAll("/", "");
    const hasTranslation =
      defaultLang !== l &&
      routes[l] !== undefined &&
      routes[l][pathName] !== undefined;
    const translatedPath = hasTranslation ? "/" + routes[l][pathName] : path;
    const translatedPathReplaced = translatedPath.replaceAll(".", "/");

    return !showDefaultLang && l === defaultLang
      ? translatedPathReplaced
      : `/${l}${translatedPathReplaced}`;
  };
}

// Add support for multi-tier routes by checking the entire pathname and skipping the language directory
export function getRouteFromUrl(url: URL): string | undefined {
  const pathname = new URL(url)?.pathname;

  const path = pathname
    ?.split("/")
    .filter(Boolean)
    .filter((part) => !Object.keys(languages).includes(part))
    .join(".");

  if (!path) {
    return undefined;
  }

  const currentLang = getLangFromUrl(url);

  if (defaultLang === currentLang) {
    const route = Object.values(routes)[0];
    const pathTyped = path as keyof typeof route;
    return pathTyped in route ? pathTyped : undefined
  }

  const getKeyByValue = (
    obj: Record<string, string>,
    value: string,
  ): string | undefined => {
    return Object.keys(obj).find((key) => obj[key] === value);
  };

  const reversedKey = getKeyByValue(routes[currentLang], path);

  if (reversedKey !== undefined) {
    return reversedKey;
  }

  return undefined;
}

Translate routes with trailingSlash and base support

The official documentation and the recipe above only works without page configuration trailingSlash or deviant base. For example, if you want to use trailingSlash: always, see the integration source code here. Or the live preview.