Better I18NBetter I18N

Locale URL Utilities

URL path manipulation utilities for locale-aware routing

@better-i18n/core includes a set of locale-aware URL utilities that work with any framework. These are the same utilities used internally by the Next.js and TanStack Start SDKs.

Convention: The default locale has no URL prefix (e.g., /about for English), while non-default locales are prefixed (e.g., /tr/about for Turkish). This follows the same pattern as next-intl and Paraglide JS.

Setup

All utilities require a LocaleConfig object:

import type { LocaleConfig } from "@better-i18n/core";

const config: LocaleConfig = {
  locales: ["en", "tr", "de", "fr"],
  defaultLocale: "en",
};
PropertyTypeDescription
localesstring[]All supported locale codes
defaultLocalestringDefault locale (no URL prefix)

extractLocale

Extracts locale from URL pathname's first segment. Returns null for the default locale (no prefix).

import { extractLocale } from "@better-i18n/core";

extractLocale("/tr/about", config);    // "tr"
extractLocale("/de/settings", config); // "de"
extractLocale("/about", config);       // null (default locale)
extractLocale("/", config);            // null
ParameterTypeDescription
pathnamestringURL pathname
configLocaleConfigLocale configuration

Returns: string | null — Locale code or null for default


getLocaleFromPath

Gets the effective locale from URL, always returning a string. Unlike extractLocale, this never returns null.

import { getLocaleFromPath } from "@better-i18n/core";

getLocaleFromPath("/tr/about", config); // "tr"
getLocaleFromPath("/about", config);    // "en" (default)
getLocaleFromPath("/", config);         // "en" (default)
ParameterTypeDescription
pathnamestringURL pathname
configLocaleConfigLocale configuration

Returns: string — Always a valid locale code


hasLocalePrefix

Checks if pathname has a locale prefix in the first segment.

import { hasLocalePrefix } from "@better-i18n/core";

hasLocalePrefix("/tr/about", config);   // true
hasLocalePrefix("/about", config);      // false
hasLocalePrefix("/unknown/page", config); // false

removeLocalePrefix

Removes locale prefix from pathname. Returns the path unchanged if no locale prefix is present.

import { removeLocalePrefix } from "@better-i18n/core";

removeLocalePrefix("/tr/about", config);    // "/about"
removeLocalePrefix("/de/settings", config); // "/settings"
removeLocalePrefix("/about", config);       // "/about" (unchanged)
removeLocalePrefix("/tr", config);          // "/"

addLocalePrefix

Adds locale prefix to pathname. Default locale gets no prefix (convention).

import { addLocalePrefix } from "@better-i18n/core";

addLocalePrefix("/about", "tr", config); // "/tr/about"
addLocalePrefix("/about", "en", config); // "/about" (default = no prefix)
addLocalePrefix("/", "de", config);      // "/de"
ParameterTypeDescription
pathnamestringURL pathname
localestringTarget locale
configLocaleConfigLocale configuration

replaceLocaleInPath

Replaces or adds locale in pathname. The main utility for locale switching in URLs.

import { replaceLocaleInPath } from "@better-i18n/core";

// Switch from default to Turkish
replaceLocaleInPath("/about", "tr", config);       // "/tr/about"

// Switch from Turkish to German
replaceLocaleInPath("/tr/about", "de", config);    // "/de/about"

// Switch to default (removes prefix)
replaceLocaleInPath("/tr/about", "en", config);    // "/about"

// Switch between non-default locales
replaceLocaleInPath("/de/settings", "fr", config); // "/fr/settings"

Language Switcher Example

import { replaceLocaleInPath } from "@better-i18n/core";
import type { LocaleConfig } from "@better-i18n/core";

const config: LocaleConfig = {
  locales: ["en", "tr", "de"],
  defaultLocale: "en",
};

function LanguageSwitcher({ currentPath }: { currentPath: string }) {
  return (
    <nav>
      {config.locales.map((locale) => (
        <a
          key={locale}
          href={replaceLocaleInPath(currentPath, locale, config)}
        >
          {locale.toUpperCase()}
        </a>
      ))}
    </nav>
  );
}

createLocalePath

Creates a reusable path builder with fixed configuration. Useful when you need to generate many localized paths.

import { createLocalePath } from "@better-i18n/core";

const localePath = createLocalePath(config);

localePath("/about", "tr");    // "/tr/about"
localePath("/about", "en");    // "/about"
localePath("/about");          // "/about" (uses default locale)
ParameterTypeDescription
configLocaleConfigLocale configuration

Returns: (path: string, locale?: string) => string

import { createLocalePath } from "@better-i18n/core";

const localePath = createLocalePath({
  locales: ["en", "tr", "de"],
  defaultLocale: "en",
});

function Navigation({ locale }: { locale: string }) {
  return (
    <nav>
      <a href={localePath("/", locale)}>Home</a>
      <a href={localePath("/about", locale)}>About</a>
      <a href={localePath("/pricing", locale)}>Pricing</a>
      <a href={localePath("/contact", locale)}>Contact</a>
    </nav>
  );
}

On this page