Next.js (SSG) & i18n

214 Views
No Comments

This article will introduce how to use multiple languages ​​in the nextjs-ssg framework with examples.

At present, NextJS’s support for SSG is not perfect, and there are still many unsupported functions, including multi-language routing. In SSR mode, you can distinguish page languages ​​by path through simple configuration. However, SSG lacks corresponding support.

Next, we will start to implement the steps:

Install related dependent libraries

#npm
npm i i18next i18next-ssg next-i18next
#yarn
yarn add i18next i18next-ssg next-i18next

Create configuration file

//Create the project root directory: next-i18next.config.js
module.exports = {
  i18n: {
    locales: ['en', 'zh'],//多语言配置
    defaultLocale: 'en',//默认语言
  },
  localePath: './public/locales', // 语言文件存放位置
};

Modify configuration file

//next.config.ts
import type { NextConfig } from "next";
const { i18n } = require('./next-i18next.config');

const nextConfig: NextConfig = {
  //...Add the following configuration
  env: {
    NEXT_PUBLIC_I18N: i18n
  },
};

export default nextConfig;

Create translation resources

//Create translation resources for each language in the public/locales directory /public/locales/[language]/common.json
{
  "home_text_title":"Online Tools-It Resource Hub",
  "welcome": "Welcome to My Site",
  "description": "Static Site with i18n"
}

Add default language build configuration

// Create [[...paths]].tsx in the same directory as _app.tsx
export { getStaticProps, getStaticPaths } from "i18next-ssg/Redirect";
import { useRootPathRedirect } from "i18next-ssg";

export default function Page() {
  useRootPathRedirect();
  return <div>Redirecting...</div>;
}

The final directory structure is as follows

-public
--locales
---en
----common.json
---zh
----common.json
-src
--pages
---_app.tsx
---_document.tsx
---[[...paths]].tsx
---[locale]
----index.tsx
----Other pages

Translate the page

I will just post one of my layout files for reference, including language switching.

const {t} = useTranslation("common")
{t("home_text_title")}

import { AppBar, Toolbar, Typography, Box, Container, Button, Menu, MenuItem, IconButton } from '@mui/material'
import { Language as LanguageIcon, KeyboardArrowDown } from '@mui/icons-material'
import Link from 'next/link'
import { useState } from 'react'

import { I18NLink, setUserLocale, useLocaleSwitcher, useTranslation } from 'i18next-ssg';

const localeMap: Record<Locale, string> = {
    en: "English",
    zh: " 中文 ",
};

const LanguageDropdown = ({
    options,
    currentLabel
}: {
    options: {
        label: string;
        path: string;
        locale: Locale;
    }[];
    currentLabel: string;
}) => {
    const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
    const open = Boolean(anchorEl);

    const handleClick = (event: React.MouseEvent<HTMLElement>) => {
        setAnchorEl(event.currentTarget);
    };

    const handleClose = () => {
        setAnchorEl(null);
    };

    return (
        <>
            <Button
                onClick={handleClick}
                startIcon={<LanguageIcon />}
                endIcon={<KeyboardArrowDown />}
                sx={{
                    color: 'white',
                    textTransform: 'none',
                    borderRadius: 2,
                    px: 2,
                    py: 1,
                    '&:hover': {
                        backgroundColor: 'rgba(255, 255, 255, 0.1)',
                    }
                }}
            >
                {currentLabel}
            </Button>
            <Menu
                anchorEl={anchorEl}
                open={open}
                onClose={handleClose}
                PaperProps={{
                    sx: {
                        backgroundColor: 'rgba(255, 255, 255, 0.95)',
                        backdropFilter: 'blur(10px)',
                        border: '1px solid rgba(255, 255, 255, 0.2)',
                        borderRadius: 2,
                        mt: 1,
                        minWidth: 120,
                    }
                }}
            >
                {options.map(({ label, path, locale }) => (
                    <MenuItem 
                        key={path} 
                        onClick={() => {
                            setUserLocale(locale);
                            handleClose();
                        }}
                        sx={{
                            '&:hover': {
                                backgroundColor: 'rgba(99, 102, 241, 0.1)',
                            }
                        }}
                    >
                        <Link href={path} style={{ textDecoration: 'none', color: 'inherit' }}>
                            {label}
                        </Link>
                    </MenuItem>
                ))}
            </Menu>
        </>
    );
};

export default function Layout({ children }: { children: React.ReactNode }) {
    const { label, options } = useLocaleSwitcher({ localeMap });
    const { t } = useTranslation("common")
    return (
        <>
            <AppBar 
                position="fixed" 
                sx={{
                    background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
                    backdropFilter: 'blur(10px)',
                    backgroundColor: 'rgba(102, 126, 234, 0.9)',
                    borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
                    boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)',
                    zIndex: 1100,
                }}
            >
                <Toolbar sx={{ minHeight: 64 }}>
                    <Typography 
                        variant="h5" 
                        component="div" 
                        sx={{ 
                            flexGrow: 1,
                            fontWeight: 600,
                            background: 'linear-gradient(45deg, #ffffff 30%, #f0f9ff 90%)',
                            WebkitBackgroundClip: 'text',
                            WebkitTextFillColor: 'transparent',
                            textShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
                        }}
                    >
                        {t("home_text_title")}
                    </Typography>
                    <LanguageDropdown options={options} currentLabel={label} />
                </Toolbar>
            </AppBar>
            
            {/* 添加顶部间距以避免内容被固定导航栏遮挡 */}
            <Box sx={{ mt: 8 }} />
            
            <Container 
                maxWidth="md" 
                sx={{ 
                    mt: 4, 
                    mb: 4,
                    px: { xs: 2, sm: 3 },
                }}
            >
                {children}
            </Container>
            
            <Box 
                sx={{ 
                    textAlign: 'center', 
                    py: 4,
                    background: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)',
                    borderTop: '1px solid rgba(0, 0, 0, 0.05)',
                    mt: 'auto',
                }}
            >
                <I18NLink href="/">
<Typography 
                    variant="body2" 
                    sx={{ 
                        color: '#64748b',
                        fontWeight: 500,
                    }}
                >
                    © 2025 IT 资源网  -  专业的技术资源平台
                </Typography>
                </I18NLink>
                
            </Box>
        </>
    )
}

Final effect

Next.js (SSG) & i18n
Next.js (SSG) & i18n
END
 0
Comment(No Comments)