Skip to content

Commit 378b943

Browse files
committed
proxy: detect language
1 parent dd5b613 commit 378b943

File tree

15 files changed

+484
-50
lines changed

15 files changed

+484
-50
lines changed

examples/i18n-app/.gitignore

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
27+
# local env files
28+
.env.local
29+
.env.development.local
30+
.env.test.local
31+
.env.production.local
32+
33+
# vercel
34+
.vercel

examples/i18n-app/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Internationalized Routing
2+
3+
This example shows how to create internationalized pages using Next.js and the i18n routing feature. It shows a normal page, a non-dynamic `getStaticProps` page, a dynamic `getStaticProps` page, and a `getServerSideProps` page.
4+
5+
For further documentation on this feature see the [Internationalized Routing docs](https://nextjs.org/docs/advanced-features/i18n-routing).
6+
7+
## Preview
8+
9+
Preview the example live on [StackBlitz](http://stackblitz.com/):
10+
11+
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/i18n-routing)
12+
13+
## Deploy your own
14+
15+
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
16+
17+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/i18n-routing&project-name=i18n-routing&repository-name=i18n-routing)
18+
19+
## How to use
20+
21+
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
22+
23+
```bash
24+
npx create-next-app --example i18n-routing i18n-app
25+
# or
26+
yarn create next-app --example i18n-routing i18n-app
27+
```
28+
29+
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).

examples/i18n-app/main.tf

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
terraform {
2+
required_providers {
3+
aws = {
4+
source = "hashicorp/aws"
5+
version = "~> 3.0"
6+
}
7+
}
8+
}
9+
10+
# Main region where the resources should be created in
11+
# Should be close to the location of your viewers
12+
provider "aws" {
13+
region = "eu-central-1"
14+
}
15+
16+
# Provider used for creating the Lambda@Edge function which must be deployed
17+
# to us-east-1 region (Should not be changed)
18+
provider "aws" {
19+
alias = "global_region"
20+
region = "us-east-1"
21+
}
22+
23+
module "tf_next" {
24+
source = "dealmore/next-js/aws"
25+
26+
deployment_name = "terraform-next-js-example-complete"
27+
28+
providers = {
29+
aws.global_region = aws.global_region
30+
}
31+
32+
# Uncomment when using in the cloned monorepo for tf-next development
33+
# source = "../.."
34+
# debug_use_local_packages = true
35+
}
36+
37+
output "cloudfront_domain_name" {
38+
value = module.tf_next.cloudfront_domain_name
39+
}

examples/i18n-app/next.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
i18n: {
3+
locales: ['en', 'fr-FR', 'nl'],
4+
defaultLocale: 'en',
5+
},
6+
}

examples/i18n-app/package.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "terraform-next-js-example-i18n-routing",
3+
"version": "1.0.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "next",
7+
"build": "next build",
8+
"tf-next": "tf-next build",
9+
"start": "next start"
10+
},
11+
"dependencies": {
12+
"next": "latest",
13+
"react": "^17.0.2",
14+
"react-dom": "^17.0.2"
15+
},
16+
"devDependencies": {
17+
"tf-next": "latest"
18+
},
19+
"license": "MIT"
20+
}

examples/i18n-app/pages/gsp/[slug].js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import Link from 'next/link'
2+
import { useRouter } from 'next/router'
3+
4+
export default function GspPage(props) {
5+
const router = useRouter()
6+
const { defaultLocale, isFallback, query } = router
7+
8+
if (isFallback) {
9+
return 'Loading...'
10+
}
11+
12+
return (
13+
<div>
14+
<h1>getStaticProps page</h1>
15+
<p>Current slug: {query.slug}</p>
16+
<p>Current locale: {props.locale}</p>
17+
<p>Default locale: {defaultLocale}</p>
18+
<p>Configured locales: {JSON.stringify(props.locales)}</p>
19+
20+
<Link href="/gsp">
21+
<a>To getStaticProps page</a>
22+
</Link>
23+
<br />
24+
25+
<Link href="/gssp">
26+
<a>To getServerSideProps page</a>
27+
</Link>
28+
<br />
29+
30+
<Link href="/">
31+
<a>To index page</a>
32+
</Link>
33+
<br />
34+
</div>
35+
)
36+
}
37+
38+
export const getStaticProps = ({ locale, locales }) => {
39+
return {
40+
props: {
41+
locale,
42+
locales,
43+
},
44+
}
45+
}
46+
47+
export const getStaticPaths = ({ locales }) => {
48+
const paths = []
49+
50+
for (const locale of locales) {
51+
paths.push({ params: { slug: 'first' }, locale })
52+
paths.push({ params: { slug: 'second' }, locale })
53+
}
54+
55+
return {
56+
paths,
57+
fallback: true,
58+
}
59+
}

examples/i18n-app/pages/gsp/index.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import Link from 'next/link'
2+
import { useRouter } from 'next/router'
3+
4+
export default function GspPage(props) {
5+
const router = useRouter()
6+
const { defaultLocale } = router
7+
8+
return (
9+
<div>
10+
<h1>getStaticProps page</h1>
11+
<p>Current locale: {props.locale}</p>
12+
<p>Default locale: {defaultLocale}</p>
13+
<p>Configured locales: {JSON.stringify(props.locales)}</p>
14+
15+
<Link href="/gsp/first">
16+
<a>To dynamic getStaticProps page</a>
17+
</Link>
18+
<br />
19+
20+
<Link href="/gssp">
21+
<a>To getServerSideProps page</a>
22+
</Link>
23+
<br />
24+
25+
<Link href="/">
26+
<a>To index page</a>
27+
</Link>
28+
<br />
29+
</div>
30+
)
31+
}
32+
33+
export const getStaticProps = ({ locale, locales }) => {
34+
return {
35+
props: {
36+
locale,
37+
locales,
38+
},
39+
}
40+
}

examples/i18n-app/pages/gssp.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import Link from 'next/link'
2+
import { useRouter } from 'next/router'
3+
4+
export default function GsspPage(props) {
5+
const router = useRouter()
6+
const { defaultLocale } = router
7+
8+
return (
9+
<div>
10+
<h1>getServerSideProps page</h1>
11+
<p>Current locale: {props.locale}</p>
12+
<p>Default locale: {defaultLocale}</p>
13+
<p>Configured locales: {JSON.stringify(props.locales)}</p>
14+
15+
<Link href="/gsp">
16+
<a>To getStaticProps page</a>
17+
</Link>
18+
<br />
19+
20+
<Link href="/gsp/first">
21+
<a>To dynamic getStaticProps page</a>
22+
</Link>
23+
<br />
24+
25+
<Link href="/">
26+
<a>To index page</a>
27+
</Link>
28+
<br />
29+
</div>
30+
)
31+
}
32+
33+
export const getServerSideProps = ({ locale, locales }) => {
34+
return {
35+
props: {
36+
locale,
37+
locales,
38+
},
39+
}
40+
}

examples/i18n-app/pages/index.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Link from 'next/link'
2+
import { useRouter } from 'next/router'
3+
4+
export default function IndexPage(props) {
5+
const router = useRouter()
6+
const { locale, locales, defaultLocale } = router
7+
8+
return (
9+
<div>
10+
<h1>Index page</h1>
11+
<p>Current locale: {locale}</p>
12+
<p>Default locale: {defaultLocale}</p>
13+
<p>Configured locales: {JSON.stringify(locales)}</p>
14+
15+
<Link href="/gsp">
16+
<a>To getStaticProps page</a>
17+
</Link>
18+
<br />
19+
20+
<Link href="/gsp/first">
21+
<a>To dynamic getStaticProps page</a>
22+
</Link>
23+
<br />
24+
25+
<Link href="/gssp">
26+
<a>To getServerSideProps page</a>
27+
</Link>
28+
<br />
29+
</div>
30+
)
31+
}

packages/proxy/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"postpack": "rm ./LICENSE ./third-party-licenses.txt"
1717
},
1818
"dependencies": {
19-
"@vercel/routing-utils": "^1.9.1",
19+
"@hapi/accept": "^5.0.2",
20+
"@vercel/routing-utils": "1.11.2",
2021
"abort-controller": "^3.0.0",
2122
"node-fetch": "^2.6.1"
2223
},
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { CloudFrontHeaders } from 'aws-lambda';
2+
3+
import { detectLocale } from '../util/detect-locale';
4+
5+
describe('detectLocale', () => {
6+
const cookieName = 'NEXT_LOCALE';
7+
8+
test('get locale from cookie', () => {
9+
const localeValue = 'nl';
10+
11+
const headers: CloudFrontHeaders = {
12+
cookie: [
13+
{
14+
key: 'Cookie',
15+
value: `name=value; name2=value2; name3=value3`,
16+
},
17+
{
18+
key: 'Cookie',
19+
value: `name=value; ${cookieName}=${localeValue}; name3=value3`,
20+
},
21+
{
22+
key: 'Cookie',
23+
value: `name=value; name2=value2; name3=value3`,
24+
},
25+
],
26+
};
27+
28+
const localeOptions = {
29+
cookie: cookieName,
30+
redirect: {
31+
en: '/',
32+
'fr-FR': '/fr-FR',
33+
nl: '/nl',
34+
},
35+
};
36+
37+
const locale = detectLocale(headers, { locale: localeOptions });
38+
expect(locale).toBe(localeValue);
39+
});
40+
41+
describe('Detect locale from header', () => {
42+
const localeOptions = {
43+
cookie: cookieName,
44+
redirect: {
45+
en: '/',
46+
'fr-FR': '/fr-FR',
47+
nl: '/nl',
48+
},
49+
};
50+
const preferredLocaleFromHeader = [
51+
['fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5', 'fr-FR'],
52+
['*', 'en'],
53+
['de', ''],
54+
['en-US, en', 'en'],
55+
['af;q=0.9, ar;q=0.8, de;q=0.7, *;q=0.5', 'en'],
56+
];
57+
test.each(preferredLocaleFromHeader)(
58+
'ExpectedLocale from "%s" should be "%s"',
59+
(headerValue, expectedLocale) => {
60+
const headers: CloudFrontHeaders = {
61+
'accept-language': [
62+
{
63+
key: 'Accept-Language',
64+
value: headerValue,
65+
},
66+
],
67+
};
68+
69+
const locale = detectLocale(headers, { locale: localeOptions });
70+
expect(locale).toBe(expectedLocale);
71+
}
72+
);
73+
});
74+
});

0 commit comments

Comments
 (0)