React 和 i18n - 通过在 URL 中添加语言环境进行翻译
React and i18n - translate by adding the locale in the URL
你好,我正在构建一个演示应用程序来学习 React,我有点被翻译过程困住了。我想做的是拥有一个多语言网站,默认语言 "Greek" 和辅助语言 "English"。当启用希腊语时,URL 不应在 URL 中包含任何语言环境,但当启用英语时,URLS 应重写为 /en/。
i18n 配置
import translationEn from './locales/en/translation';
import translationEl from './locales/el/translation';
import Constants from './Utility/Constants';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'el',
debug: true,
ns: ['translations'],
defaultNS: 'translations',
detection: {
order: ['path'],
lookupFromPathIndex: 0,
},
resources: {
el: {
translations: translationEl
},
en: {
translations: translationEn
}
},
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
}
}, () => {
// Why the fuck this doesnt work automatically with fallbackLng ??
if (!Constants.allowed_locales.includes(i18n.language)) {
i18n.changeLanguage(Constants.default_locale);
}
return;
});
i18n.on('languageChanged', function (lng) {
// if somehow it get injected
if (!Constants.allowed_locales.includes(i18n.language)) {
i18n.changeLanguage(Constants.default_locale);
}
// if the language we switched to is the default language we need to remove the /en from URL
if (Constants.default_locale === lng) {
Constants.allowed_locales.map((item) => {
if (window.location.pathname.includes("/" + item)) {
let newUrl = window.location.pathname.replace("/" + item, "");
window.location.replace(newUrl);
}
})
} else { // Add the /en in the URL
// @todo: add elseif for more than 2 langs because this works only for default + 1 more language
let newUrl = "/" + lng + window.location.pathname;
window.location.replace(newUrl);
}
});
export default i18n;
我使用 Language Detector
插件从 path 进行检测,因此它可以从 URL 解析语言环境。现在没有我在初始化时添加的回调函数,如果 url 是 www.example.com/en/
或 www.example.com/el
或 www.example.com/en/company
,LanguageDetector
将正确设置语言。但是,如果我直接访问 www.example.com/company
(在首先访问主页之前设置语言环境)i18n 会将 locale/language 设置为 "company"
!!!
fallbackLng
有一个选项,我认为如果 LanguageDetector
没有检测到它,它会将语言设置为您配置的语言,但似乎没有设置可用语言的选项或默认语言为 i18n(或者我是个白痴,找不到它)所以 LanguageDetector 设置他在 URL 中找到的任何内容。为了解决这个问题,我在上面添加了一个常量文件和回调函数。
Contants.js
const Constants = {
"allowed_locales": ['el','en'],
"default_locale": 'el'
}
export default Constants;
我还添加了一个在 LanguageChange 上触发的事件处理程序,因此如果英语处于活动状态,它将用 /en/ 重写 URL,如果希腊语处于活动状态,则删除 /el/。
index.js
ReactDOM.render(
<BrowserRouter>
<I18nextProvider i18n={i18n}>
<App/>
</I18nextProvider>
</BrowserRouter>
,
document.getElementById('root')
);
App.js
class App extends React.Component {
render() {
return (
<div className="App">
<Suspense fallback="loading">
<Header {...this.props}/>
<Routes {...this.props} />
</Suspense>
</div>
);
}
}
export default withTranslation('translations')(App);
index.js 和 App.js
没什么特别的
页眉组件
class Header extends React.Component {
linkGenerator(link) {
// if the current language is the default language dont add the lang prefix
const languageLocale = this.props.i18n.options.fallbackLng[0] === this.props.i18n.language ? null : this.props.i18n.language;
return languageLocale ? "/" + languageLocale + link : link;
}
render() {
return (
<div className="header">
<Navbar bg="light" expand="lg">
<Container>
<Navbar.Brand className="logo" href="/"> <Image src="/assets/logo.png" rounded/>
</Navbar.Brand>
{/*Used For Mobile Navigation*/}
<Navbar.Toggle aria-controls="basic-navbar-nav"/>
<Navbar.Collapse id="basic-navbar-nav" className="float-right">
<Nav className="ml-auto">
<Nav.Link as={NavLink} exact to={this.linkGenerator("/")}>{this.props.t('menu.home')}</Nav.Link>
<Nav.Link as={NavLink} to={this.linkGenerator("/company")}>{this.props.t('menu.company')}</Nav.Link>
</Nav>
<Nav className="mr-auto">
{this.props.i18n.language !== "el" ? <button onClick={() => this.props.i18n.changeLanguage('el')}>gr</button>
: null}
{this.props.i18n.language !== "en" ? <button onClick={() => this.props.i18n.changeLanguage('en')}>en</button>
: null}
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
</div>
)
}
}
export default Header
为了用语言环境创建菜单的 urls,我创建了 linkGenerator function
最后,在我处理所有路由的路由组件中,我在实际 url 之前添加了一个常量,因此它适用于所有这些 /page
, /el/page
, /en/page
路由组件
import React from 'react';
import {Switch, Route} from 'react-router-dom';
import CompanyPage from './Pages/CompanyPage';
import HomePage from './Pages/HomePage';
import NotFound from './Pages/NotFound';
class Routes extends React.Component {
render() {
const localesString = "/:locale(el|en)?";
return (
<Switch>
<Route exact path={localesString + "/"} component={HomePage}/>
<Route path={localesString + "/company"} component={CompanyPage}/>
<Route component={NotFound}/>
</Switch>
);
}
}
export default Routes
代码以某种方式工作,但充满了黑客攻击,如:
额外的配置文件(constants.js)
回调函数,将语言从 "company" 更改为默认语言环境。 (这会触发 2 次页面重新加载)
函数处理菜单和路由中的语言环境
等..
是否有任何 "build-in" 功能或更好的方法可以在不使用上述 hack 的情况下实现相同的目的?
我需要同样的东西,我发现您可以设置 i18n.init 选项的 whitelist
属性 并指定支持的语言。之后,如果您在 detection
选项中设置 checkWhitelist: true
,则 LanguageDetector 将仅匹配白名单数组中存在的语言。
无论如何,您仍然需要定义 languageChanged 事件以便在匹配默认语言时重定向页面,但是,如果它是另一种受支持的语言,则不再需要重定向(至少我不需要)。
我做的最后一件事不同的是,我首先定义了 languageChanged 事件,然后才调用 i18n.init,这样它就会触发事件第一次设置语言。
这是我的代码:
i18n.js
import i18n from 'i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
i18n.on('languageChanged', function (lng) {
// if the language we switched to is the default language we need to remove the /en from URL
if (lng === i18n.options.fallbackLng[0]) {
if (window.location.pathname.includes('/' + i18n.options.fallbackLng[0])) {
const newUrl = window.location.pathname.replace('/' + i18n.options.fallbackLng[0], '')
window.location.replace(newUrl)
}
}
})
i18n
.use(LanguageDetector)
.init({
resources: {
en: {
translation: require('./translations/en.js').default
},
pt: {
translation: require('./translations/pt.js').default
}
},
whitelist: ['en', 'pt'],
fallbackLng: ['en'],
detection: {
order: ['path'],
lookupFromPathIndex: 0,
checkWhitelist: true
},
interpolation: {
escapeValue: false,
formatSeparator: '.'
}
})
export default i18n
App.js
import { Route, Switch } from "react-router-dom";
import AboutPage from "./AboutPage";
import HomePage from "./Homepage/HomePage";
import NotFoundPage from "./NotFoundPage";
import PropTypes from "prop-types";
import React from "react";
import { hot } from "react-hot-loader";
import {
Collapse,
Navbar,
NavbarToggler,
NavbarBrand,
Nav,
NavItem,
NavLink } from 'reactstrap';
import i18n from "../i18n";
const baseRouteUrl = "/:locale(pt|en)?";
export const baseUrl = i18n.language === 'en' ? '' : '/'+i18n.language;
class App extends React.Component {
state = {
isOpen: false
}
render() {
return (
<div>
<div>
<Navbar color="grey" expand="md">
<NavbarBrand href="/">Testing</NavbarBrand>
<Nav className="ml-auto" navbar>
<NavItem>
<NavLink href={baseUrl + "/"}>Home</NavLink>
</NavItem>
<NavItem>
<NavLink href={baseUrl + "/about/"}>About</NavLink>
</NavItem>
</Nav>
</Navbar>
</div>
<Switch>
<Route exact path={baseRouteUrl + "/"} component={HomePage} />
<Route path={baseRouteUrl + "/about"} component={AboutPage} />
<Route component={NotFoundPage} />
</Switch>
</div>
);
}
}
App.propTypes = {
children: PropTypes.element
};
export default hot(module)(App);
在我的例子中,当我需要翻译一些东西时,我导入我的 i18n.js
并像这样调用相应的键:
<div>{i18n.t('home.bannerStart')}</div>
当您为 Router
定义 basename
时,您不需要添加到每个路由。
// in your case use el instead of en
export const baseUrl = () => return (i18n.language === "en" ? "" : "/" + i18n.language);
路由器应该是这样的:
<Router basename={baseUrl()}>
...
</Router>
您可以在需要创建 link 时调用 baseUrl()
。另请注意,<Link>
已经考虑了 basename
,因此您不必定义它。
对于语言更改,请查看我的其他答案 here
你好,我正在构建一个演示应用程序来学习 React,我有点被翻译过程困住了。我想做的是拥有一个多语言网站,默认语言 "Greek" 和辅助语言 "English"。当启用希腊语时,URL 不应在 URL 中包含任何语言环境,但当启用英语时,URLS 应重写为 /en/。
i18n 配置
import translationEn from './locales/en/translation';
import translationEl from './locales/el/translation';
import Constants from './Utility/Constants';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'el',
debug: true,
ns: ['translations'],
defaultNS: 'translations',
detection: {
order: ['path'],
lookupFromPathIndex: 0,
},
resources: {
el: {
translations: translationEl
},
en: {
translations: translationEn
}
},
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
}
}, () => {
// Why the fuck this doesnt work automatically with fallbackLng ??
if (!Constants.allowed_locales.includes(i18n.language)) {
i18n.changeLanguage(Constants.default_locale);
}
return;
});
i18n.on('languageChanged', function (lng) {
// if somehow it get injected
if (!Constants.allowed_locales.includes(i18n.language)) {
i18n.changeLanguage(Constants.default_locale);
}
// if the language we switched to is the default language we need to remove the /en from URL
if (Constants.default_locale === lng) {
Constants.allowed_locales.map((item) => {
if (window.location.pathname.includes("/" + item)) {
let newUrl = window.location.pathname.replace("/" + item, "");
window.location.replace(newUrl);
}
})
} else { // Add the /en in the URL
// @todo: add elseif for more than 2 langs because this works only for default + 1 more language
let newUrl = "/" + lng + window.location.pathname;
window.location.replace(newUrl);
}
});
export default i18n;
我使用 Language Detector
插件从 path 进行检测,因此它可以从 URL 解析语言环境。现在没有我在初始化时添加的回调函数,如果 url 是 www.example.com/en/
或 www.example.com/el
或 www.example.com/en/company
,LanguageDetector
将正确设置语言。但是,如果我直接访问 www.example.com/company
(在首先访问主页之前设置语言环境)i18n 会将 locale/language 设置为 "company"
!!!
fallbackLng
有一个选项,我认为如果 LanguageDetector
没有检测到它,它会将语言设置为您配置的语言,但似乎没有设置可用语言的选项或默认语言为 i18n(或者我是个白痴,找不到它)所以 LanguageDetector 设置他在 URL 中找到的任何内容。为了解决这个问题,我在上面添加了一个常量文件和回调函数。
Contants.js
const Constants = {
"allowed_locales": ['el','en'],
"default_locale": 'el'
}
export default Constants;
我还添加了一个在 LanguageChange 上触发的事件处理程序,因此如果英语处于活动状态,它将用 /en/ 重写 URL,如果希腊语处于活动状态,则删除 /el/。
index.js
ReactDOM.render(
<BrowserRouter>
<I18nextProvider i18n={i18n}>
<App/>
</I18nextProvider>
</BrowserRouter>
,
document.getElementById('root')
);
App.js
class App extends React.Component {
render() {
return (
<div className="App">
<Suspense fallback="loading">
<Header {...this.props}/>
<Routes {...this.props} />
</Suspense>
</div>
);
}
}
export default withTranslation('translations')(App);
index.js 和 App.js
没什么特别的页眉组件
class Header extends React.Component {
linkGenerator(link) {
// if the current language is the default language dont add the lang prefix
const languageLocale = this.props.i18n.options.fallbackLng[0] === this.props.i18n.language ? null : this.props.i18n.language;
return languageLocale ? "/" + languageLocale + link : link;
}
render() {
return (
<div className="header">
<Navbar bg="light" expand="lg">
<Container>
<Navbar.Brand className="logo" href="/"> <Image src="/assets/logo.png" rounded/>
</Navbar.Brand>
{/*Used For Mobile Navigation*/}
<Navbar.Toggle aria-controls="basic-navbar-nav"/>
<Navbar.Collapse id="basic-navbar-nav" className="float-right">
<Nav className="ml-auto">
<Nav.Link as={NavLink} exact to={this.linkGenerator("/")}>{this.props.t('menu.home')}</Nav.Link>
<Nav.Link as={NavLink} to={this.linkGenerator("/company")}>{this.props.t('menu.company')}</Nav.Link>
</Nav>
<Nav className="mr-auto">
{this.props.i18n.language !== "el" ? <button onClick={() => this.props.i18n.changeLanguage('el')}>gr</button>
: null}
{this.props.i18n.language !== "en" ? <button onClick={() => this.props.i18n.changeLanguage('en')}>en</button>
: null}
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
</div>
)
}
}
export default Header
为了用语言环境创建菜单的 urls,我创建了 linkGenerator function
最后,在我处理所有路由的路由组件中,我在实际 url 之前添加了一个常量,因此它适用于所有这些 /page
, /el/page
, /en/page
路由组件
import React from 'react';
import {Switch, Route} from 'react-router-dom';
import CompanyPage from './Pages/CompanyPage';
import HomePage from './Pages/HomePage';
import NotFound from './Pages/NotFound';
class Routes extends React.Component {
render() {
const localesString = "/:locale(el|en)?";
return (
<Switch>
<Route exact path={localesString + "/"} component={HomePage}/>
<Route path={localesString + "/company"} component={CompanyPage}/>
<Route component={NotFound}/>
</Switch>
);
}
}
export default Routes
代码以某种方式工作,但充满了黑客攻击,如:
额外的配置文件(constants.js)
回调函数,将语言从 "company" 更改为默认语言环境。 (这会触发 2 次页面重新加载)
函数处理菜单和路由中的语言环境
等..
是否有任何 "build-in" 功能或更好的方法可以在不使用上述 hack 的情况下实现相同的目的?
我需要同样的东西,我发现您可以设置 i18n.init 选项的 whitelist
属性 并指定支持的语言。之后,如果您在 detection
选项中设置 checkWhitelist: true
,则 LanguageDetector 将仅匹配白名单数组中存在的语言。
无论如何,您仍然需要定义 languageChanged 事件以便在匹配默认语言时重定向页面,但是,如果它是另一种受支持的语言,则不再需要重定向(至少我不需要)。
我做的最后一件事不同的是,我首先定义了 languageChanged 事件,然后才调用 i18n.init,这样它就会触发事件第一次设置语言。
这是我的代码:
i18n.js
import i18n from 'i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
i18n.on('languageChanged', function (lng) {
// if the language we switched to is the default language we need to remove the /en from URL
if (lng === i18n.options.fallbackLng[0]) {
if (window.location.pathname.includes('/' + i18n.options.fallbackLng[0])) {
const newUrl = window.location.pathname.replace('/' + i18n.options.fallbackLng[0], '')
window.location.replace(newUrl)
}
}
})
i18n
.use(LanguageDetector)
.init({
resources: {
en: {
translation: require('./translations/en.js').default
},
pt: {
translation: require('./translations/pt.js').default
}
},
whitelist: ['en', 'pt'],
fallbackLng: ['en'],
detection: {
order: ['path'],
lookupFromPathIndex: 0,
checkWhitelist: true
},
interpolation: {
escapeValue: false,
formatSeparator: '.'
}
})
export default i18n
App.js
import { Route, Switch } from "react-router-dom";
import AboutPage from "./AboutPage";
import HomePage from "./Homepage/HomePage";
import NotFoundPage from "./NotFoundPage";
import PropTypes from "prop-types";
import React from "react";
import { hot } from "react-hot-loader";
import {
Collapse,
Navbar,
NavbarToggler,
NavbarBrand,
Nav,
NavItem,
NavLink } from 'reactstrap';
import i18n from "../i18n";
const baseRouteUrl = "/:locale(pt|en)?";
export const baseUrl = i18n.language === 'en' ? '' : '/'+i18n.language;
class App extends React.Component {
state = {
isOpen: false
}
render() {
return (
<div>
<div>
<Navbar color="grey" expand="md">
<NavbarBrand href="/">Testing</NavbarBrand>
<Nav className="ml-auto" navbar>
<NavItem>
<NavLink href={baseUrl + "/"}>Home</NavLink>
</NavItem>
<NavItem>
<NavLink href={baseUrl + "/about/"}>About</NavLink>
</NavItem>
</Nav>
</Navbar>
</div>
<Switch>
<Route exact path={baseRouteUrl + "/"} component={HomePage} />
<Route path={baseRouteUrl + "/about"} component={AboutPage} />
<Route component={NotFoundPage} />
</Switch>
</div>
);
}
}
App.propTypes = {
children: PropTypes.element
};
export default hot(module)(App);
在我的例子中,当我需要翻译一些东西时,我导入我的 i18n.js
并像这样调用相应的键:
<div>{i18n.t('home.bannerStart')}</div>
当您为 Router
定义 basename
时,您不需要添加到每个路由。
// in your case use el instead of en
export const baseUrl = () => return (i18n.language === "en" ? "" : "/" + i18n.language);
路由器应该是这样的:
<Router basename={baseUrl()}>
...
</Router>
您可以在需要创建 link 时调用 baseUrl()
。另请注意,<Link>
已经考虑了 basename
,因此您不必定义它。
对于语言更改,请查看我的其他答案 here