Skip to content

Commit

Permalink
feat(app): redirect by accept-language header (#282)
Browse files Browse the repository at this point in the history
* feat(app): add/modify tests for locale redirection

* feat(app): redirect by accept language
  • Loading branch information
Kohei Asai authored Mar 28, 2021
1 parent e32abb7 commit ae2ca9d
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 33 deletions.
81 changes: 52 additions & 29 deletions e2e/locale-navigation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,77 @@
import { Browser, Page, Response, webkit } from "playwright";
import { Browser, BrowserContext, Page, Response, webkit } from "playwright";

describe("Locale Navigation", () => {
let browser: Browser;
let page: Page;

beforeAll(async () => {
browser = await webkit.launch();
});

beforeEach(async () => {
page = await browser.newPage();
});

afterEach(async () => {
await page.close();
});

afterAll(async () => {
await browser.close();
});

describe("/", () => {
it("redirects with locale search params when it was missing", async () => {
let initialResponse!: Response;
describe.each([
["en-US", "en-US"],
["en-GB", "en-US"],
["en", "en-US"],
["ja-JP", "ja-JP"],
["ja", "ja-JP"],
["fr-FR", "en-US"],
])("when you prefer %s", (preferredLocale, expectedLocale) => {
let context!: BrowserContext;
let page: Page;

page.on("response", (response) => {
if (response.request().url() === "http://localhost:3000/") {
initialResponse = response;
}
beforeAll(async () => {
context = await browser.newContext({ locale: preferredLocale });
page = await context.newPage();
});

await page.goto("http://localhost:3000/");
beforeEach(async () => {
page = await context.newPage();
});

expect(initialResponse.status()).toBe(308);
expect(page.url()).toBe("http://localhost:3000/?hl=en-US");
});
afterEach(async () => {
await page.close();
});

it("doesn't redirect when the valid locale search params is given", async () => {
let initialResponse!: Response;
afterAll(async () => {
await context.close();
});

it("redirects with locale search params when it was missing", async () => {
let initialResponse!: Response;

page.on("response", (response) => {
if (response.request().url() === "http://localhost:3000/") {
initialResponse = response;
}
});

await page.goto("http://localhost:3000/");

page.on("response", (response) => {
if (response.request().url() === "http://localhost:3000/?hl=en-US") {
initialResponse = response;
}
expect(initialResponse.status()).toBe(308);
expect(page.url()).toBe(`http://localhost:3000/?hl=${expectedLocale}`);
});

await page.goto("http://localhost:3000/?hl=en-US");
it("doesn't redirect when the valid locale search params is given", async () => {
let initialResponse!: Response;

expect(initialResponse.status()).toBe(200);
expect(page.url()).toBe("http://localhost:3000/?hl=en-US");
page.on("response", (response) => {
if (
response.request().url() ===
`http://localhost:3000/?hl=${expectedLocale}`
) {
initialResponse = response;
}
});

await page.goto(`http://localhost:3000/?hl=${expectedLocale}`);

expect(initialResponse.status()).toBe(200);
expect(page.url()).toBe(`http://localhost:3000/?hl=${expectedLocale}`);
});
});
});
});
17 changes: 17 additions & 0 deletions helpers/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as acceptLanguageParser from "accept-language-parser";
import { AVAILABLE_LOCALES, FALLBACK_LOCALE } from "../constants/locale";

export function getLocaleFromQuery(query: Record<string, any>): string | null {
Expand All @@ -13,3 +14,19 @@ export function getLocaleFromQueryWithFallback(
): string {
return getLocaleFromQuery(query) ?? FALLBACK_LOCALE;
}

export function getBestMatchedLocaleFromLanguageRange(
languageRange: string
): string | null {
return acceptLanguageParser.pick(AVAILABLE_LOCALES, languageRange, {
loose: true,
});
}

export function getBestMatchedLocaleOrFallbackFromLanguageRange(
languageRange: string
): string {
return (
getBestMatchedLocaleFromLanguageRange(languageRange) ?? FALLBACK_LOCALE
);
}
24 changes: 24 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@linaria/webpack4-loader": "^3.0.0-beta.1",
"@sentry/react": "^6.2.3",
"@sentry/tracing": "^6.2.3",
"accept-language-parser": "^1.5.0",
"cheerio": "^1.0.0-rc.5",
"date-fns": "^2.17.0",
"graphql": "^15.5.0",
Expand Down Expand Up @@ -60,6 +61,7 @@
"@storybook/react": "^6.1.20",
"@testing-library/jest-dom": "^5.11.10",
"@testing-library/react": "^11.2.5",
"@types/accept-language-parser": "^1.5.1",
"@types/github-slugger": "^1.3.0",
"@types/jest": "^26.0.21",
"@types/mustache": "^4.1.1",
Expand Down
16 changes: 14 additions & 2 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import { CACHE_HEADER_VALUE } from "../constants/cache";
import { AVAILABLE_LOCALES } from "../constants/locale";
import { CommonServerSideProps } from "../core/ssr-props";
import { useOrigin } from "../global-hooks/url";
import { getLocaleFromQuery } from "../helpers/i18n";
import {
getBestMatchedLocaleOrFallbackFromLanguageRange,
getLocaleFromQuery,
} from "../helpers/i18n";
import { getOriginFromRequest } from "../helpers/next";
import { getIndexPageJson, getPostEntryListJson } from "../services/cms-json";
import { getIntlMessages } from "../services/translation";
Expand Down Expand Up @@ -108,7 +111,16 @@ export const getServerSideProps: GetServerSideProps<ServerSideProps> = async ({
}

if (!locale) {
return { redirect: { destination: `/?hl=en-US`, permanent: true } };
const redirectLocale = getBestMatchedLocaleOrFallbackFromLanguageRange(
req.headers["accept-language"] ?? ""
);

return {
redirect: {
destination: `/?hl=${redirectLocale}`,
permanent: true,
},
};
}

const origin = getOriginFromRequest(req);
Expand Down
14 changes: 12 additions & 2 deletions pages/posts/[slug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import { CACHE_HEADER_VALUE } from "../../constants/cache";
import { AVAILABLE_LOCALES } from "../../constants/locale";
import { CommonServerSideProps } from "../../core/ssr-props";
import { useOrigin } from "../../global-hooks/url";
import { getLocaleFromQuery } from "../../helpers/i18n";
import {
getBestMatchedLocaleOrFallbackFromLanguageRange,
getLocaleFromQuery,
} from "../../helpers/i18n";
import { getOriginFromRequest } from "../../helpers/next";
import { getPostEntryListJson, getPostJson } from "../../services/cms-json";
import { getIntlMessages } from "../../services/translation";
Expand Down Expand Up @@ -146,8 +149,15 @@ export const getServerSideProps: GetServerSideProps<ServerSideProps> = async ({
}

if (!locale) {
const redirectLocale = getBestMatchedLocaleOrFallbackFromLanguageRange(
req.headers["accept-language"] ?? ""
);

return {
redirect: { destination: `/posts/${slug}?hl=en-US`, permanent: true },
redirect: {
destination: `/posts/${slug}?hl=${redirectLocale}`,
permanent: true,
},
};
}

Expand Down

1 comment on commit ae2ca9d

@vercel
Copy link

@vercel vercel bot commented on ae2ca9d Mar 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.