Skip to content

Commit

Permalink
Merge pull request #3381 from opral/parjs-360-pathname-strategy-optio…
Browse files Browse the repository at this point in the history
…n-to-always-prefix-default-locale

Parjs-360-pathname-strategy-option-to-always-prefix-default-locale
  • Loading branch information
samuelstroschein authored Jan 31, 2025
2 parents c61b721 + d44182a commit 45d4da6
Show file tree
Hide file tree
Showing 27 changed files with 202 additions and 68 deletions.
12 changes: 12 additions & 0 deletions inlang/packages/paraglide/paraglide-js/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# @inlang/paraglide-js

## 2.0.0-beta.17

Add support for `pathnamePrefixBaseLocale` https://github.com/opral/inlang-paraglide-js/issues/358.

```diff
await compile({
strategy: ["pathname", "cookie", "baseLocale"],
+ pathnamePrefixBaseLocale: true
})

```

## 2.0.0-beta.16

New `strategy` API. See https://github.com/opral/inlang-paraglide-js/issues/346.
Expand Down
14 changes: 14 additions & 0 deletions inlang/packages/paraglide/paraglide-js/src/compiler/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const defaultCompilerOptions = {
emitPrettierIgnore: true,
strategy: ["cookie", "variable", "baseLocale"],
cookieName: "PARAGLIDE_LOCALE",
pathnamePrefixDefaultLocale: false,
} as const satisfies Partial<CompilerOptions>;

export type CompilerOptions = {
Expand Down Expand Up @@ -82,6 +83,19 @@ export type CompilerOptions = {
* @default true
*/
emitGitIgnore?: boolean;
/**
* Whether to prefix the default locale to the pathname.
*
* For incremental adoption reasons, the base locale is not
* prefixed by default. A path like `/page` will be served by
* is matched as base locale.
*
* Setting the option to `true` will prefix the default locale to the
* pathname. `/page` will become `/en/page` (if the base locale is en).
*
* @default false
*/
pathnamePrefixDefaultLocale?: boolean;
/**
* The file-structure of the compiled output.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export function generateLocaleModules(
compilerOptions: {
strategy: NonNullable<CompilerOptions["strategy"]>;
cookieName: NonNullable<CompilerOptions["cookieName"]>;
pathnamePrefixDefaultLocale: NonNullable<
CompilerOptions["pathnamePrefixDefaultLocale"]
>;
}
): Record<string, string> {
const fileExt = "js";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export function generateMessageModules(
compilerOptions: {
strategy: NonNullable<CompilerOptions["strategy"]>;
cookieName: NonNullable<CompilerOptions["cookieName"]>;
pathnamePrefixDefaultLocale: NonNullable<
CompilerOptions["pathnamePrefixDefaultLocale"]
>;
}
): Record<string, string> {
const output: Record<string, string> = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isLocale } from "./is-locale.js";
import { locales } from "./locales.js";
import { locales } from "./variables.js";

/**
* Asserts that the input is a locale.
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,22 @@ export function createRuntimeFile(args: {
compilerOptions: {
strategy: NonNullable<CompilerOptions["strategy"]>;
cookieName: NonNullable<CompilerOptions["cookieName"]>;
pathnamePrefixDefaultLocale: NonNullable<
CompilerOptions["pathnamePrefixDefaultLocale"]
>;
};
}): string {
return `
${injectCode("./base-locale.js").replace(`<base-locale>`, `${args.baseLocale}`)}
${injectCode("./locales.js").replace(`["<base-locale>"]`, `["${args.locales.join('", "')}"]`)}
${injectCode("./strategy.js").replace(`["variable"]`, `["${args.compilerOptions.strategy.join('", "')}"]`)}
${injectCode("./cookie-name.js").replace(`<cookie-name>`, `${args.compilerOptions.cookieName}`)}
${injectCode("./variables.js")
.replace(`<base-locale>`, `${args.baseLocale}`)
.replace(`["<base-locale>"]`, `["${args.locales.join('", "')}"]`)
.replace(`["variable"]`, `["${args.compilerOptions.strategy.join('", "')}"]`)
.replace(`<cookie-name>`, `${args.compilerOptions.cookieName}`)
.replace(
`pathnamePrefixDefaultLocale = false`,
`pathnamePrefixDefaultLocale = ${args.compilerOptions.pathnamePrefixDefaultLocale}`
)}
/**
* Define the \`getLocale()\` function.
Expand Down Expand Up @@ -122,6 +127,7 @@ export async function createRuntimeForTesting(args: {
compilerOptions?: {
strategy?: CompilerOptions["strategy"];
cookieName?: CompilerOptions["cookieName"];
pathnamePrefixDefaultLocale?: CompilerOptions["pathnamePrefixDefaultLocale"];
};
}): Promise<Runtime> {
const file = createRuntimeFile({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ test("handles paths with no segments after locale", async () => {
const runtime = await createRuntimeForTesting({
baseLocale: "en",
locales: ["en", "de"],
compilerOptions: {
pathnamePrefixDefaultLocale: true,
},
});

const path = "/en/";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cookieName } from "./cookie-name.js";
import { cookieName } from "./variables.js";

/**
* Extracts a cookie from the document.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { isLocale } from "./is-locale.js";
import { baseLocale, pathnamePrefixDefaultLocale } from "./variables.js";

/**
* Extracts the locale from a given pathname.
Expand All @@ -13,9 +14,21 @@ import { isLocale } from "./is-locale.js";
*/
export function extractLocaleFromPathname(pathname) {
const [, maybeLocale] = pathname.split("/");
if (isLocale(maybeLocale)) {

if (pathnamePrefixDefaultLocale && maybeLocale === baseLocale) {
return baseLocale;
} else if (
pathnamePrefixDefaultLocale === false &&
maybeLocale === baseLocale
) {
return undefined;
} else if (isLocale(maybeLocale)) {
return maybeLocale;
} else {
// it's not possible to match deterministically
// if the path is the baseLocale at this point
// or not. users should use the strategy api
// to set the base locale as fallback
return undefined;
}

return undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,45 @@ test("returns undefined if isLocale is false", async () => {
const locale = runtime.extractLocaleFromPathname(path);
expect(locale).toBe(undefined);
});

test("returns undefined if pathnamePrefixDefaultLocale = false and base locale is contained path", async () => {
const runtime = await createRuntimeForTesting({
baseLocale: "en",
locales: ["en", "de"],
compilerOptions: {
pathnamePrefixDefaultLocale: false,
},
});

const path = "/en/about";
const locale = runtime.extractLocaleFromPathname(path);
expect(locale).toBe(undefined);
});

test("returns the locale if pathnamePrefixDefaultLocale = true and base locale is contained path", async () => {
const runtime = await createRuntimeForTesting({
baseLocale: "en",
locales: ["en", "de"],
compilerOptions: {
pathnamePrefixDefaultLocale: true,
},
});

const path = "/en/about";
const locale = runtime.extractLocaleFromPathname(path);
expect(locale).toBe("en");
});

test("returns undefined if pathnamePrefixDefaultLocale = false and path doesn't contain a locale", async () => {
const runtime = await createRuntimeForTesting({
baseLocale: "en",
locales: ["en", "de"],
compilerOptions: {
pathnamePrefixDefaultLocale: false,
},
});

const path = "/about";
const locale = runtime.extractLocaleFromPathname(path);
expect(locale).toBe(undefined);
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { assertIsLocale } from "./assert-is-locale.js";
import { baseLocale } from "./base-locale.js";
import { cookieName } from "./cookie-name.js";
import { baseLocale, cookieName, strategy } from "./variables.js";
import { extractLocaleFromPathname } from "./extract-locale-from-pathname.js";
import { strategy } from "./strategy.js";

/**
* Detect a locale from a request.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ test("returns the locale from the pathname", async () => {
locales: ["en"],
compilerOptions: {
strategy: ["pathname"],
pathnamePrefixDefaultLocale: true,
},
});
const request = new Request("http://example.com/en/home");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { assertIsLocale } from "./assert-is-locale.js";
import { baseLocale } from "./base-locale.js";
import { baseLocale, strategy } from "./variables.js";
import { extractLocaleFromCookie } from "./extract-locale-from-cookie.js";
import { extractLocaleFromPathname } from "./extract-locale-from-pathname.js";
import { strategy } from "./strategy.js";

/**
* This is a fallback to get started with a custom
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { locales } from "./locales.js";
import { locales } from "./variables.js";

/**
* Check if something is an available locale.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { baseLocale } from "./base-locale.js";
import { baseLocale, pathnamePrefixDefaultLocale } from "./variables.js";
import { getLocale } from "./get-locale.js";
import { extractLocaleFromPathname } from "./extract-locale-from-pathname.js";

Expand All @@ -25,21 +25,21 @@ import { extractLocaleFromPathname } from "./extract-locale-from-pathname.js";
* @example
* <a href={localizePath('/home')}>Home</a>
*
* @param {string} path
* @param {string} pathname
* @param {Object} [options] - Optional parameters.
* @param {Locale} [options.locale] - The locale to use for the path.
* @returns {string}
*/
export function localizePath(path, options) {
export function localizePath(pathname, options) {
const locale = options?.locale ?? getLocale();
const hasLocale = extractLocaleFromPathname(path);
const hasLocale = extractLocaleFromPathname(pathname);
const pathWithoutLocale = hasLocale
? "/" + path.split("/").slice(2).join("/")
: path;
? "/" + pathname.split("/").slice(2).join("/")
: pathname;

if (locale === baseLocale) {
if (locale === baseLocale && pathnamePrefixDefaultLocale === false) {
return pathWithoutLocale;
} else if (path === "/" || pathWithoutLocale === "/") {
} else if (pathname === "/" || pathWithoutLocale === "/") {
return `/${locale}`;
} else {
return `/${locale}${pathWithoutLocale}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,13 @@ test("adds no trailing slash for the root path", async () => {
expect(l10nPath).toBe("/de");
});

test("removes the base locale from the path", async () => {
test("removes the base locale from the path if pathnamePrefixDefaultLocale = false", async () => {
const runtime = await createRuntimeForTesting({
baseLocale: "en",
locales: ["en", "de"],
compilerOptions: {
pathnamePrefixDefaultLocale: false,
},
});

const path = "/de/about";
Expand All @@ -51,6 +54,21 @@ test("removes the base locale from the path", async () => {
expect(l10nPath).toBe("/about");
});

test("adds the base locale to the path if pathnamePrefixDefaultLocale = true", async () => {
const runtime = await createRuntimeForTesting({
baseLocale: "en",
locales: ["en", "de"],
compilerOptions: {
pathnamePrefixDefaultLocale: true,
},
});

const path = "/de/about";
const l10nPath = runtime.localizePath(path, { locale: "en" });

expect(l10nPath).toBe("/en/about");
});

test("does not add a slash suffix if it's the root path that is already localized", async () => {
const runtime = await createRuntimeForTesting({
baseLocale: "en",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { cookieName } from "./cookie-name.js";
import { cookieName, strategy } from "./variables.js";
import { localizePath } from "./localize-path.js";
import { strategy } from "./strategy.js";

/**
* Set the locale.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
export type Runtime = {
baseLocale: Locale;
locales: Readonly<Locale[]>;
strategy: typeof import("./strategy.js").strategy;
cookieName: typeof import("./cookie-name.js").cookieName;
strategy: typeof import("./variables.js").strategy;
cookieName: typeof import("./variables.js").cookieName;
getLocale: typeof import("./get-locale.js").getLocale;
setLocale: typeof import("./set-locale.js").setLocale;
defineGetLocale: (fn: () => Locale) => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* The project's base locale.
*
* @example
* if (locale === baseLocale) {
* // do something
* }
*/
export const baseLocale = "<base-locale>";

/**
* The project's locales that have been specified in the settings.
*
* @example
* if (locales.includes(userSelectedLocale) === false) {
* throw new Error('Locale is not available');
* }
*/
export const locales = /** @type {const} */ (["<base-locale>"]);

/** @type {string} */
export const cookieName = "<cookie-name>";

/**
* @type {Array<"cookie" | "baseLocale" | "pathname" | "variable">}
*/
export const strategy = ["variable"];

/** @type {boolean} */
export const pathnamePrefixDefaultLocale = false;
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ export default defineConfig({
sveltekit(),
paraglideSveltekit({
project: './project.inlang',
outdir: './src/lib/paraglide'
outdir: './src/lib/paraglide',
strategy: ['pathname', 'baseLocale'],
pathnamePrefixDefaultLocale: false
})
]
});
Loading

0 comments on commit 45d4da6

Please sign in to comment.