最近有朋友让我建一个有日文版和英文版的网站。 我从来没有写过一个国际化的网站,但我对本地化应该如何工作有强烈的看法。 因此,这是使用 Cosmic JS(一种无头 CMS)在 Gatsby 中实现国际化的一种方法。
一点上下文
有很多方法可以实现本地化,但始终没有灵丹妙药。 每种方法都以不同的方式解决问题。 所以这是我的背景:
- 我想建立一个维护成本尽可能低的网站。
- 内容作者没有任何编程经验
- 站点的维护成本应尽可能低。
- 网站不应强迫用户使用网站的一个版本。
最后一点对我来说非常重要。 当您在国外时,某些网站会强制您使用其网站的本地版本。 当 [array of multinational companies] 强迫我访问网站的 [long array of languages I don't understand] 版本时,它让我发疯。 我对页面的自动翻译有同样的问题。 如果我想自动翻译我的网站,我可以使用出色的 Google 翻译 Chrome 扩展程序 。
本网站适用于日语和英语用户。 所以网站的所有页面都应该有英文版和日文版。 如果用户想要更改网站的当前版本,她可以单击导航栏中的语言菜单。
我的方法
Gatsby 和 React 提供 许多工具来处理本地化 (l10n) 和国际化 (i18n) 。
我首先使用 gatsby-plugin-i18n 来轻松生成路由。
例如,/page/team.ja.js 将生成以下 URL:/ja/team(ja 是日本的语言代码)。
这是一个非常好的插件,但问题是它不是程序化的。 我必须为每种语言编写一个新文件。 在每个文件中,我必须进行特定的 GraphQL 查询来获取数据。 例如,如果我向我的 CMS 引入一种新语言,我必须使用新的语言扩展再次创建所有路由。
因此,我决定在没有任何插件的情况下构建 l10n。 这个项目的所有代码都可以在 https://github.com/alligatorio/kodou 上找到。
在这种情况下,内容作者完全负责本地化。 当她编写网站的日文版时,她应该确保日期格式正确。 这就是我们不使用依赖于国际化 API 的 react-intl 的原因,这将是未来帖子的主题。
设置 Cosmic JS
Cosmic JS 是一个很棒的无头 CMS 选项,允许您在创建新对象类型时激活本地化。
不要忘记选择优先区域设置,否则新对象将无法保存。
在我们的新站点中,我们有一个团队页面,因此我们创建了一个团队成员对象。 当我们创建一个新的团队成员时,我们现在可以选择它的语言。
现在要从 Gatsby 访问这些数据,我们需要添加 gatsby-source-cosmicjs 源插件:
$ yarn add gatsby-source-cosmicjs
然后我们需要通过在plugins中添加以下代码来配置gatsby-config.js使用gatsby-source-cosmicjs。
模块:gatsby-config.js
{
resolve: "gatsby-source-cosmicjs",
options: {
bucketSlug: process.env.COSMIC_BUCKET,
// We add the 'team-members' object type to be able to fetch it later
objectTypes: ["team-members"],
// If you have enabled read_key to fetch data (optional).
apiAccess: {
read_key: process.env.COSMIC_ENV_KEY,
}
}
}
在我们的其余代码中,我们可以通过运行以下命令从 Cosmic JS 访问团队成员数据:
graphql(`
{
allCosmicjsTeamMembers {
edges {
# Here we have the structure of out `team-members` object
node {
title
locale
content
metadata {
profile_picture {
imgix_url
}
}
}
}
}
}
`)
现在本地化魔法发生了。
生成本地化页面
我希望我的朋友能够自己做任何他想做的改变。 所以我完全放弃了 /pages 目录,转而使用 /templates 目录。 Gatsby 模板允许我们拥有可重用的内容并以编程方式创建页面; 这正是我们需要做的!
在查看模板文件之前,让我们看看如何从 Cosmic JS 获取数据以创建新页面。
模块:gatsby-node.js
// langs contains the languages of our blog and default langKey is the default language of the site
// To be fully programmatic we could calculate langs
// here langs = ['en', 'ja'] and defaultLangKey = 'en'
const { langs, defaultLangKey } = require('../config/languages')
const path = require(`path`)
const { localizeUrl, createLanguagesObject } = require('../utils/localization')
exports.createPages = async ({ actions, graphql }) => {
const { createPage } = actions
const result = await graphql(`
{
allCosmicjsTeamMembers {
edges {
node {
title
locale
content
metadata {
profile_picture {
imgix_url
}
}
}
}
}
}
`)
if (result.errors) {
console.error(result.errors)
}
// Creates a profiles object with out site's languages
const profiles = createLanguagesObject(langs)
// profiles = {
// 'en': [],
// 'ja': []
// }
// converting the raw cosmic data into a more useable data structure
result.data.allCosmicjsTeamMembers.edges.forEach(({ node }) => {
profiles[node.locale].push(node)
})
// profiles = {
// 'en': [...all English profiles],
// 'ja': [...all Japanese profiles]
// }
// we create a new page for each language
langs.forEach(lang =>{
createPage({
// the localizeUrl function creates a url which takes into consideration what the default language is
path: localizeUrl(lang, defaultLangKey, '/team'),
component: path.resolve(`src/templates/team.js`),
context: {
profiles: profiles[lang]
}
})
})
}
此代码将使用路径 /ja/team 和 /team 创建两个新页面(没有 /en,因为我们将英语设置为默认语言)。
如您所见,createPage 将具有 3 个字段 path、component 和 context 的对象作为参数。 路径只是我们希望新页面拥有的路径。 component 是我们要使用的模板。 context 是我们要传递给模板的数据。 在这里,我们传递以所需语言编写的配置文件。
模板
让我们看一下我们的团队模板。
import React from "react"
import Layout from "../components/layout"
import SEO from "../components/seo"
const TeamPage = (props) => {
// We will see about pageContext in the next section
const {profiles} = props.pageContext
return (
<Layout location={props.location}>
<SEO title="Team" />
<h1>Team</h1>
// Iterating trough the array of profiles
{profiles.map((profile,i)=>(
<div key={i} className="columns">
<div className="column">
// Here are some nice profile pictures of our team members
<div className="square-image" style={{backgroundImage: `url("${profile.metadata.profile_picture.imgix_url}")`}}/>
</div>
<div className="column is-two-thirds">
<div className="team-member-title">{profile.title}</div>
// Here is some html content we get from Cosmic
<div dangerouslySetInnerHTML={{ __html: profile.content }}/>
</div>
</div>
)
)}
</Layout>
)
}
export default TeamPage
总而言之,上面的代码采用 profiles 属性,它是我们从 Cosmic JS 获得的配置文件数组。 每个配置文件都有一个配置文件图片对象、一个 title 和一个 content 字段。 content 实际上是一个 HTML 字符串,所以我们必须使用 dangerouslySetInnerHTML 属性来设置它。
要使此模板正常工作,请务必提前准备 CSS 文件以获得一致的结果。 我的朋友将无法在 Cosmic 的 WYSIWYG 中添加类名或 ID。
还有很多话要说和做:
- 创建导航栏和位置感知布局
- 如何使用国际化 API
- 如何将用户软重定向到他们的网站版本
您可以浏览 Github 存储库 以了解我如何解决这些问题并在 kodou.me 上查看结果。 或者使用 Alligator.io 查看我们是否上传了有关该主题的一些新内容。 但我认为在一篇文章中处理的内容已经很多。 以上,我希望这对您建立自己的国际化网站有所帮助,敬请期待更多! 😉