Gatsby 사이트에 i18n 구현

This article was originally published on Obytes blog.





이 문서에서는 react-intl 및 Gatsby을 사용하여 React context API 사이트에 i18n(국제화)을 구현할 것입니다. 이 문서에서는 영어와 아랍어만 다루지만 시작하기 전에 원하는 경우 더 많은 언어를 추가할 수 있습니다. , 먼저 구현 방법을 계획해 보겠습니다.

1- 사용자의 기본 언어 감지

2- 사용자의 기본 언어에 따라 언어, 콘텐츠 방향 및 글꼴 모음을 자동으로 전환합니다.

3- 사용자는 여전히 선호하는 언어를 선택할 수 있습니다.

CLI 도구를 사용하여 새 Gatsby 사이트를 생성하여 시작하겠습니다.

gatsby new gatsby-i18n-example && cd gatsby-i18n-example/

그런 다음 필요한 라이브러리를 설치합니다(저는 yarn를 사용하고 있지만 자유롭게 사용할 수 있습니다npm).

I'm installing recompose too to separate logic from the component and keep the code clean (Feel free not to use it but We highly recommend it), as well as styled-components beta v4 to handle css in js (Feel free not use it too but We highly recommend it) and a simple google fonts gatsby plugin gatsby-plugin-google-fonts



yarn add react-intl recompose styled-components@next babel-plugin-styled-components gatsby-plugin-styled-components gatsby-plugin-google-fonts

시작하기 전에 먼저 아래와 같이 더 나은 방식으로 파일을 구조화하겠습니다.

.
+-- src
    +-- components
    |   |
    |   +-- common
    |   |   +-- Head
    |   |   |   |
    |   |   |   +-- index.jsx
    |   |   +-- Container
    |   |   |   |
    |   |   |   +-- index.jsx
    |   |   +-- Context
    |   |   |   |
    |   |   |   +-- index.jsx
    |   |   +-- Layout
    |   |   |   |
    |   |   |   +-- index.jsx
    |   |   |   +-- Provider.jsx
    |   |   |   +-- layout.css
    |   |   +-- Trigger
    |   |   |   |
    |   |   |   +-- index.jsx
    |   |   +-- index.js
    |   +-- theme
    |   |   +-- Header
    |   |   |   |
    |   |   |   +-- index.jsx
    +-- messages
    |   |
    |   +-- ar.json
    |   +-- en.json
    +-- pages
        |
        +-- index.js
        +-- 404.js
        +-- about.js

Context 구성 요소 내에서 컨텍스트를 생성하고 기본값으로 en를 갖는 것으로 시작하겠습니다.

import React from 'react'

export const Context = React.createContext('en')

이제 전역 상태를 하위 항목인 소비자에게 전달하는 공급자 구성 요소를 살펴보겠습니다.

Provider is a React component that allows Consumers to subscribe to context changes.



import React from 'react'
import { compose, withState, withHandlers, lifecycle } from 'recompose'
import { Context } from '../Context'

const Provider = ({ children, lang, toggleLanguage }) => (
    <Context.Provider value={
        { lang, toggleLanguage: () => toggleLanguage() }
    }>
        {children}
    </Context.Provider>
)

const enhance = compose(
    withState('lang', 'handleLanguage', 'en'),
    withHandlers({
        toggleLanguage: ({ lang, handleLanguage }) => () => {
            if (lang === 'ar') {
                handleLanguage('en')
                localStorage.setItem('lang', 'en')
            } else {
                handleLanguage('ar')
                localStorage.setItem('lang', 'ar')
            }
        }
    }),
    lifecycle({
        componentDidMount() {
            const localLang = localStorage.getItem('lang')
            if (localLang) {
                this.props.handleLanguage(localLang)
            } else {
                this.props.handleLanguage(navigator.language.split('-')[0])
            }
        }
    })
)

export default enhance(Provider)

이렇게 하면 lang가 포함된 값과 toggleLanguage라는 언어를 토글하는 함수에 액세스할 수 있도록 모든 구성 요소가 래핑되며 구성 요소 아래에는 논리가 있습니다.
lang의 기본값으로 en를 초기화했지만 구성 요소가 마운트될 때 변경될 수 있습니다. localStorage가 사용 가능한지 확인합니다. true인 경우 해당 값을 lang 상태에 할당합니다. 그렇지 않은 경우 사용자의 브라우저를 감지합니다. 기본 언어를 선택하고 값을 분할하여 해당 언어가 포함된 첫 번째 항목을 가져옵니다.

이제 Layout 구성 요소로 이동합니다.
  • 영어와 아랍어 json 데이터를 모두 가져옵니다
  • .
  • IntlProvider와 함께 우리가 사용할 콘텐츠를 래핑합니다. react-intl 내장 구성 요소
  • 뿐만 아니라 가져오기Context 및 컨슈머로 콘텐츠를 래핑하여 전역 상태
  • 에 액세스할 수 있습니다.
  • 마지막으로 위에서 만든 Provider로 모든 것을 래핑합니다.

  • import React from 'react'
    import styled from 'styled-components'
    import ar from 'react-intl/locale-data/ar'
    import en from 'react-intl/locale-data/en'
    import { addLocaleData, IntlProvider } from 'react-intl'
    import localEng from '../../../messages/en.json'
    import localAr from '../../../messages/ar.json'
    import { Context } from '../Context'
    import Provider from './Provider'
    import Header from '../../theme/Header'
    import './layout.css'
    
    addLocaleData(ar, en)
    
    const Layout = ({ children }) => (
        <Provider>
            <Context.Consumer>
                {({ lang }) => (
                    <IntlProvider locale={lang} messages={lang === 'en' ? localEng : localAr}>
                        <Global lang={lang}>
                            <Header />
                            {children}
                        </Global>
                    </IntlProvider>
                )}
            </Context.Consumer>
        </Provider>
    )
    
    const Global = styled.div`
        font-family: 'Roboto', sans-serif;
        ${({ lang }) => lang === 'ar' && `
            font-family: 'Cairo', sans-serif;    
        `}
    `
    
    export { Layout }
    

    우리는 글꼴 변경을 처리하기 위해 Global 구성 요소를 사용했다는 것을 언급하는 것을 잊었습니다. 따라서 언어가 영어로 설정되면 Roboto가 되고 아랍어로 설정되면 Cairo가 됩니다.

    작동하도록 만드는 모든 것이 준비되었으므로 헤더에 버튼을 추가하여 언어를 전환해 보겠습니다.

    import React from 'react'
    import styled from 'styled-components'
    import { Link } from 'gatsby'
    import { FormattedMessage } from 'react-intl'
    import { Trigger, Container } from '../../common'
    
    const Header = () => (
        <StyledHeader>
            <Navbar as={Container}>
                <Link to="/">
                    <FormattedMessage id="logo_text" />
                </Link>
                <Links>
                    <Link to="/">
                        <FormattedMessage id="home" />
                    </Link>
                    <Link to="/about">
                        <FormattedMessage id="about" />
                    </Link>
                    <Trigger />
                </Links>
            </Navbar>
        </StyledHeader>
    )
    
    // Feel free to move these to a separated styles.js file and import them above
    
    const StyledHeader = styled.div`
        padding: 1rem 0;
        background: #00BCD4;
    `
    const Navbar = styled.div`
        display: flex;
        align-items: center;
        justify-content: space-between;
        a {
            color: #fff;
            text-decoration: none;
        }
    `
    const Links = styled.div`
        display: flex;
        align-items: center;
        a {
            margin: 0 1rem;
        }
    `
    
    export default Header
    

    우리가 잘 이해할 수 있도록 언어를 변경하는 버튼을 분리했습니다.

    import React from 'react'
    import styled from 'styled-components'
    import { FormattedMessage } from 'react-intl'
    import { Context } from '../Context'
    
    const Trigger = () => (
        <Context.Consumer>
            {({ toggleLanguage }) => (
                <Button type="button" onClick={toggleLanguage}>
                    <FormattedMessage id="language" />
                </Button>
            )}
        </Context.Consumer>
    )
    
    // We recommend moving the style down below to a separate file
    
    const Button = styled.button`
        color: #fff;
        padding: .3rem 1rem;
        box-shadow: 0 4px 6px rgba(50,50,93,.11), 0 1px 3px rgba(0,0,0,.08);
        background: #3F51B5;
        border-radius: 4px;
        font-size: 15px;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: .025em;
        text-decoration: none;
        cursor: pointer;
        &:focus {
            outline: none;
        }
    `
    
    export { Trigger }
    

    이 파일에서 Context를 다시 한 번 가져와서 Consumer를 사용할 수 있으므로 전역 상태를 얻을 수 있습니다. 이제 버튼을 클릭하면 toggleLanguage 함수가 lang 값을 변경합니다.

    Gatsby 구성 파일을 가져오기 전에 컨텍스트 소비자의 lang 값에 액세스하고 아랍어인지 조건부로 확인하여 콘텐츠의 방향도 관리해 보겠습니다. true인 경우 방향은 rtl가 되고 그렇지 않으면 lrt가 됩니다. ) .

    import React from 'react'
    import { Helmet } from 'react-helmet'
    import { injectIntl } from 'react-intl'
    import { Context } from '../Context'
    
    const Head = ({ title, intl: { formatMessage } }) => (
        <Context.Consumer>
            {({ lang }) => (
                <Helmet>
                    <html lang={lang} dir={lang === 'ar' ? 'rtl' : 'ltr'} />
                    <title>
                        ${formatMessage({ id: title })}
                    </title>
                </Helmet>
            )}
        </Context.Consumer>
    )
    
    export default injectIntl(Head)
    

    You could include all the meta tags, opengraph, structured data and Twitter tags in this Head component like I did in the gatsby-i18n-starter



    마지막으로 사용 중인 플러그인을 gatsby-config.js 파일에 포함하고 i18n을 지원하는 일부 메시지가 포함된 더미 페이지를 준비하겠습니다.

    module.exports = {
        siteMetadata: {
            title: 'Gatsby i18n Example',
        },
        plugins: [
            'gatsby-plugin-react-helmet',
            'gatsby-plugin-styled-components',
            {
                resolve: 'gatsby-plugin-google-fonts',
                options: {
                    fonts: [
                        'Cairo',
                        'Roboto'
                    ]
                }
            },
            {
                resolve: 'gatsby-plugin-manifest',
                options: {
                    name: 'gatsby-starter-default',
                    short_name: 'starter',
                    start_url: '/',
                    background_color: '#663399',
                    theme_color: '#663399',
                    display: 'minimal-ui',
                    icon: 'src/images/gatsby-icon.png',
                },
            },
            'gatsby-plugin-offline',
        ],
    }
    

  • 홈페이지

  • import React from 'react'
    import { FormattedMessage } from 'react-intl'
    import { Layout, Container } from '../components/common'
    import Head from '../components/common/Head'
    
    const IndexPage = () => (
        <Layout>
            <>
                <Head title="welcome" />
                <Container>
                    <h2>
                        <FormattedMessage id="welcome" />
                    </h2>
                </Container>
            </>
        </Layout>
    )
    
    export default IndexPage
    

  • 페이지 소개

  • import React from 'react'
    import { FormattedMessage } from 'react-intl'
    import { Layout, Container } from '../components/common'
    import Head from '../components/common/Head'
    
    const AboutPage = () => (
        <Layout>
            <>
                <Head title="about" />
                <Container>
                    <h2>
                        <FormattedMessage id="about" />
                    </h2>
                </Container>
            </>
        </Layout>
    )
    
    export default AboutPage
    

    다음은 이 예제에서 사용하는 메시지가 포함된 두 json 파일입니다.

    {
        "language": "عربي",
        "welcome": "Welcome",
        "Logo_text": "Logo",
        "Home": "Home",
        "About": "About",
        "not_found": "404 - Page Not Found"
    }
    



    {
        "language": "English",
        "welcome": "أهلا بك",
        "Logo_text": "شعار",
        "Home": "الرئيسية",
        "About": "معلومات عنا",
        "not_found": "الصفحة غير موجودة - 404"
    }
    

    다음을 실행하여 테스트해 보겠습니다.

    yarn develop
    

    작동하는 것 같습니다 🎉, check the demo , 여기 the link to the repository가 있습니다. 후속 조치를 할 수 없는 경우 질문이 있으십니까? 댓글로 남겨주시면 최대한 빨리 답변드리겠습니다.

    Feel free to use my own gatsby-i18n-starter to easily get started with some other great features.

    좋은 웹페이지 즐겨찾기