Skip to content

Commit a35b64c

Browse files
authored
Add <meta> tags for translations (#5930)
* Add site config with domain name * Add <meta> tags for languages * Move legacy redirect out of vercel config This lets us give it a more specific subdomain per lang. * Support sites with no legacy * Fix types * Undo unrelated change
1 parent 6c096e0 commit a35b64c

File tree

5 files changed

+58
-26
lines changed

5 files changed

+58
-26
lines changed

next.config.js

+18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
* Copyright (c) Facebook, Inc. and its affiliates.
33
*/
44

5+
const siteConfig = require('./src/siteConfig').siteConfig;
6+
57
/**
68
* @type {import('next').NextConfig}
79
**/
@@ -14,6 +16,22 @@ const nextConfig = {
1416
legacyBrowsers: false,
1517
browsersListForSwc: true,
1618
},
19+
redirects() {
20+
const redirects = [];
21+
const languageCode = siteConfig.languageCode;
22+
const subdomain =
23+
languageCode === 'en' || !siteConfig.hasLegacySite
24+
? ''
25+
: languageCode + '.';
26+
return [
27+
{
28+
// Assume all *.html links are legacy site URLs.
29+
source: '/:path*(\\.html)',
30+
destination: `https://${subdomain}legacy.reactjs.org/:path*.html`,
31+
permanent: false,
32+
},
33+
];
34+
},
1735
env: {
1836
SANDPACK_BARE_COMPONENTS: process.env.SANDPACK_BARE_COMPONENTS,
1937
},

src/components/Seo.tsx

+32-17
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import * as React from 'react';
66
import Head from 'next/head';
77
import {withRouter, Router} from 'next/router';
8+
import {siteConfig} from '../siteConfig';
89

910
export interface SeoProps {
1011
title: string;
@@ -16,6 +17,16 @@ export interface SeoProps {
1617
searchOrder?: number;
1718
}
1819

20+
const deployedTranslations = [
21+
'en',
22+
// We'll add more languages when they have enough content.
23+
];
24+
25+
function getDomain(languageCode: string): string {
26+
const subdomain = languageCode === 'en' ? '' : languageCode + '.';
27+
return subdomain + 'react.dev';
28+
}
29+
1930
export const Seo = withRouter(
2031
({
2132
title,
@@ -26,29 +37,37 @@ export const Seo = withRouter(
2637
isHomePage,
2738
searchOrder,
2839
}: SeoProps & {router: Router}) => {
40+
const siteDomain = getDomain(siteConfig.languageCode);
41+
const canonicalUrl = `https://${siteDomain}${
42+
router.asPath.split(/[\?\#]/)[0]
43+
}`;
2944
const pageTitle = isHomePage ? 'React' : title + ' – React';
3045
// Twitter's meta parser is not very good.
3146
const twitterTitle = pageTitle.replace(/[<>]/g, '');
3247
return (
3348
<Head>
34-
{/* DEFAULT */}
35-
3649
<meta name="viewport" content="width=device-width, initial-scale=1" />
37-
3850
{title != null && <title key="title">{pageTitle}</title>}
3951
{description != null && (
4052
<meta name="description" key="description" content={description} />
4153
)}
42-
{/* <link rel="icon" type="image/x-icon" href={favicon} />
43-
<link rel="apple-touch-icon" href={favicon} /> @todo favicon */}
54+
<link rel="canonical" href={canonicalUrl} />
55+
<link
56+
rel="alternate"
57+
href={canonicalUrl.replace(siteDomain, getDomain('en'))}
58+
hrefLang="x-default"
59+
/>
60+
{deployedTranslations.map((languageCode) => (
61+
<link
62+
key={'alt-' + languageCode}
63+
rel="alternate"
64+
hrefLang={languageCode}
65+
href={canonicalUrl.replace(siteDomain, getDomain(languageCode))}
66+
/>
67+
))}
4468
<meta property="fb:app_id" content="623268441017527" />
45-
{/* OPEN GRAPH */}
4669
<meta property="og:type" key="og:type" content="website" />
47-
<meta
48-
property="og:url"
49-
key="og:url"
50-
content={`https://react.dev${router.asPath.split(/[\?\#]/)[0]}`}
51-
/>
70+
<meta property="og:url" key="og:url" content={canonicalUrl} />
5271
{title != null && (
5372
<meta property="og:title" content={pageTitle} key="og:title" />
5473
)}
@@ -59,14 +78,11 @@ export const Seo = withRouter(
5978
content={description}
6079
/>
6180
)}
62-
6381
<meta
6482
property="og:image"
6583
key="og:image"
66-
content={`https://react.dev${image}`}
84+
content={`https://${siteDomain}${image}`}
6785
/>
68-
69-
{/* TWITTER */}
7086
<meta
7187
name="twitter:card"
7288
key="twitter:card"
@@ -88,11 +104,10 @@ export const Seo = withRouter(
88104
content={description}
89105
/>
90106
)}
91-
92107
<meta
93108
name="twitter:image"
94109
key="twitter:image"
95-
content={`https://react.dev${image}`}
110+
content={`https://${siteDomain}${image}`}
96111
/>
97112
<meta
98113
name="google-site-verification"

src/pages/_document.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
*/
44

55
import {Html, Head, Main, NextScript} from 'next/document';
6+
import {siteConfig} from '../siteConfig';
67

78
const MyDocument = () => {
8-
// @todo specify language in HTML?
99
return (
10-
<Html lang="en">
10+
<Html lang={siteConfig.languageCode}>
1111
<Head />
1212
<body className="font-text font-medium antialiased text-lg bg-wash dark:bg-wash-dark text-secondary dark:text-secondary-dark leading-base">
1313
<script

src/siteConfig.ts src/siteConfig.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
* Copyright (c) Facebook, Inc. and its affiliates.
33
*/
44

5-
export const siteConfig = {
6-
editUrl: 'https://github.com/reactjs/react.dev/tree/main/src/pages',
5+
exports.siteConfig = {
6+
// --------------------------------------
7+
// Translations should replace these lines:
8+
languageCode: 'en',
9+
hasLegacySite: true,
10+
// --------------------------------------
711
copyright: `Copyright © ${new Date().getFullYear()} Facebook Inc. All Rights Reserved.`,
812
repoUrl: 'https://github.com/facebook/react',
913
twitterUrl: 'https://twitter.com/reactjs',

vercel.json

-5
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,6 @@
8484
"destination": "/blog/2020/12/21/data-fetching-with-react-server-components",
8585
"permanent": false
8686
},
87-
{
88-
"source": "/:path*(\\.html)",
89-
"destination": "https://legacy.reactjs.org/:path*.html",
90-
"permanent": false
91-
},
9287
{
9388
"source": "/tips/controlled-input-null-value.html",
9489
"destination": "/reference/react-dom/components/input#im-getting-an-error-a-component-is-changing-an-uncontrolled-input-to-be-controlled",

0 commit comments

Comments
 (0)