Skip to content

Commit

Permalink
fix broken i18n bits (#372)
Browse files Browse the repository at this point in the history
* fix broken i18n bits

* add i18n integration test

* changelog
  • Loading branch information
bkendall committed Jan 5, 2023
1 parent 6b4a534 commit 285f446
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 2 deletions.
2 changes: 1 addition & 1 deletion changelog.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
- Fixes issue with path resolution on Windows.
- Fixes i18n issues introduced when fixing relative rewrites.
7 changes: 6 additions & 1 deletion src/utils/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import { join } from "node:path";
import { normalizeMultiSlashes } from "./pathutils";

/**
* Returns the list of paths to check for i18n content.
Expand Down Expand Up @@ -57,6 +57,11 @@ export function i18nContentOptions(p: string, req: any): string[] {
return paths;
}

function join(...arr: string[]): string {
arr.unshift("/");
return normalizeMultiSlashes(arr.join("/"));
}

/**
* Fetches the country code from the headers object.
* @param headers headers from the request.
Expand Down
104 changes: 104 additions & 0 deletions test/integration/i18n.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Copyright (c) 2022 Google LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import * as fs from "node:fs/promises";
import * as connect from "connect";
import * as request from "supertest";

import superstatic from "../../";
import { MiddlewareOptions } from "../../src/options";
import { Configuration } from "../../src/config";

function options(): MiddlewareOptions & { config: Configuration } {
return {
fallthrough: false,
config: {
public: ".tmp",
i18n: { root: "intl" },
},
};
}

describe("i18n resolution", () => {
before(async () => {
await fs.rm(".tmp", { recursive: true, force: true });
await fs.mkdir(".tmp");
await fs.writeFile(".tmp/index.html", "normal index", "utf8");
await fs.writeFile(".tmp/404.html", "normal 404", "utf8");
await fs.mkdir(".tmp/intl");
await fs.mkdir(".tmp/intl/fr");
await fs.writeFile(".tmp/intl/fr/index.html", "french index", "utf8");
await fs.writeFile(".tmp/intl/fr/404.html", "french 404", "utf8");
});

after(async () => {
await fs.rm(".tmp", { recursive: true, force: true });
});

it("index.html", async () => {
const opts = options();

const app = connect().use(superstatic(opts));

await request(app).get("/").expect(200, "normal index");
});

it("index.html with language", async () => {
const opts = options();

const app = connect().use(superstatic(opts));

await request(app)
.get("/")
.set("accept-language", "fr")
.expect(200, "french index");
});

it("index.html with unknown language", async () => {
const opts = options();

const app = connect().use(superstatic(opts));

await request(app)
.get("/")
.set("accept-language", "de")
.expect(200, "normal index");
});

it("an unknown file", async () => {
const opts = options();

const app = connect().use(superstatic(opts));

await request(app).get("/nope").expect(404, "normal 404");
});

it("an unknown file with language", async () => {
const opts = options();

const app = connect().use(superstatic(opts));

await request(app)
.get("/nope")
.set("accept-language", "fr")
.expect(404, "french 404");
});
});
48 changes: 48 additions & 0 deletions test/unit/utils/i18n.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { expect } from "chai";
import { i18nContentOptions } from "../../../src/utils/i18n";

describe("i18nContentOptions", () => {
it("should return no files with no i18n config", () => {
const paths = i18nContentOptions("/index.html", {
superstatic: {},
headers: { "accept-language": "fr" },
});

expect(paths).to.have.members([]);
});

it("should return a list of files for language fr", () => {
const paths = i18nContentOptions("/index.html", {
superstatic: { i18n: { root: "/i18n" } },
headers: { "accept-language": "fr" },
});

expect(paths).to.have.members([
"/i18n/fr_ALL/index.html",
"/i18n/fr/index.html",
]);
});

it("should return a list of files for country ca", () => {
const paths = i18nContentOptions("/index.html", {
superstatic: { i18n: { root: "/i18n" } },
headers: { "x-country-code": "ca" },
});

expect(paths).to.have.members(["/i18n/ALL_ca/index.html"]);
});

it("should return a list of files for language fr and country ca", () => {
const paths = i18nContentOptions("/index.html", {
superstatic: { i18n: { root: "/i18n" } },
headers: { "accept-language": "fr", "x-country-code": "ca" },
});

expect(paths).to.have.members([
"/i18n/fr_ca/index.html",
"/i18n/ALL_ca/index.html",
"/i18n/fr_ALL/index.html",
"/i18n/fr/index.html",
]);
});
});

0 comments on commit 285f446

Please sign in to comment.