Skip to content

Commit 0852780

Browse files
committed
feat: add site-level i18n with EN/中文 locale switcher
Add site locale data (nav, home page, guide menu, categories, color page, code block labels) in both English and Chinese. Add locale context with localStorage persistence and toggle button in header. All site pages now read strings from locale. Router uses dual lazy imports to load the correct markdown file per locale.
1 parent 0137af5 commit 0852780

16 files changed

Lines changed: 702 additions & 547 deletions

File tree

site/src/components/code-block/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { LightCodeTheme, DarkCodeTheme } from './code-theme';
66
import * as Components from '../../../../components';
77
import CollapseTransition from '../../../../components/collapse-transition';
88
import { useTheme } from '../../../../components';
9+
import { useLocaleContext } from '../../context/locale-context';
910

1011
type Props = {
1112
children: string;
@@ -17,6 +18,7 @@ export const CodeBlock = ({ children, className, live }: Props): React.ReactElem
1718
const [showCode, setShowCode] = useState(false);
1819
const ref = useRef<HTMLDivElement | null>(null);
1920
const { resolvedTheme } = useTheme();
21+
const { siteLocale: s } = useLocaleContext();
2022
const codeTheme = (resolvedTheme === 'dark' ? DarkCodeTheme : LightCodeTheme) as typeof themes.github;
2123

2224
let language: Language = 'markup';
@@ -42,7 +44,7 @@ export const CodeBlock = ({ children, className, live }: Props): React.ReactElem
4244
</div>
4345
</CollapseTransition>
4446
<div className="code-block__controller" onClick={() => setShowCode(!showCode)}>
45-
{showCode ? 'Hide Code' : 'Show Code'}
47+
{showCode ? s.codeBlock.hideCode : s.codeBlock.showCode}
4648
</div>
4749
</LiveProvider>
4850
</div>

site/src/components/header/header.scss

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,27 @@
9090
padding: 4px 13px;
9191
}
9292

93+
&__locale-toggle {
94+
display: inline-flex;
95+
align-items: center;
96+
justify-content: center;
97+
height: 28px;
98+
padding: 0 8px;
99+
border: 1px solid var(--ty-color-border);
100+
border-radius: 14px;
101+
background: var(--ty-color-bg-elevated);
102+
color: var(--ty-color-text-secondary);
103+
cursor: pointer;
104+
transition: all 0.2s;
105+
font-size: 12px;
106+
font-weight: 500;
107+
108+
&:hover {
109+
color: var(--ty-color-primary);
110+
border-color: var(--ty-color-primary);
111+
}
112+
}
113+
93114
&__theme-toggle {
94115
display: inline-flex;
95116
align-items: center;

site/src/components/header/index.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import { NavLink } from 'react-router-dom';
44
import pkg from '../../../../package.json';
55
import { Icon, Link } from '../../../../components';
66
import { useSidebarToggle } from '../../context/sidebar-toggle-context';
7+
import { useLocaleContext } from '../../context/locale-context';
78
import { ThemeToggle } from './theme-toggle';
9+
import { LocaleToggle } from './locale-toggle';
810

911
const { version, repository } = pkg;
1012

1113
export const Header = (): React.ReactElement => {
1214
const { toggle } = useSidebarToggle();
15+
const { siteLocale: s } = useLocaleContext();
1316

1417
return (
1518
<header className="header">
@@ -35,17 +38,17 @@ export const Header = (): React.ReactElement => {
3538
<ul className="header__nav-list">
3639
<li className="header__nav-item">
3740
<NavLink end to="/" className="header__link">
38-
Home
41+
{s.nav.home}
3942
</NavLink>
4043
</li>
4144
<li className="header__nav-item">
4245
<NavLink to="/guide" className="header__link">
43-
Guide
46+
{s.nav.guide}
4447
</NavLink>
4548
</li>
4649
<li className="header__nav-item">
4750
<NavLink to="/components" className="header__link">
48-
Components
51+
{s.nav.components}
4952
</NavLink>
5053
</li>
5154
<li className="header__nav-item">
@@ -62,6 +65,9 @@ export const Header = (): React.ReactElement => {
6265
<Icon name="github" color="currentColor" size={19} />
6366
</Link>
6467
</li>
68+
<li className="header__nav-item">
69+
<LocaleToggle />
70+
</li>
6571
<li className="header__nav-item">
6672
<ThemeToggle />
6773
</li>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react';
2+
import { useLocaleContext } from '../../context/locale-context';
3+
4+
export const LocaleToggle = (): React.ReactElement => {
5+
const { locale, toggle } = useLocaleContext();
6+
const isZh = locale.locale === 'zh_CN';
7+
8+
return (
9+
<button
10+
className="header__locale-toggle"
11+
onClick={toggle}
12+
aria-label={isZh ? 'Switch to English' : '切换到中文'}
13+
title={isZh ? 'Switch to English' : '切换到中文'}>
14+
{isZh ? 'EN' : '中文'}
15+
</button>
16+
);
17+
};

site/src/containers/color/index.tsx

Lines changed: 30 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,64 @@
11
import React from 'react';
22
import './color-page.scss';
3+
import { useLocaleContext } from '../../context/locale-context';
34

45
type ColorCardProps = {
56
name: string;
67
hex: string;
8+
nameLabel: string;
9+
hexLabel: string;
710
};
811

9-
const ColorCard = ({ name, hex }: ColorCardProps): React.ReactElement => (
12+
const ColorCard = ({ name, hex, nameLabel, hexLabel }: ColorCardProps): React.ReactElement => (
1013
<div className="color-card">
1114
<div className="color-card__header" style={{ backgroundColor: hex }} />
1215
<div className="color-card__footer">
1316
<div>
14-
<p className="color-card__title">NAME</p>
17+
<p className="color-card__title">{nameLabel}</p>
1518
<p className="color-card__subtitle">{name}</p>
1619
</div>
1720
<div>
18-
<p className="color-card__title">HEX</p>
21+
<p className="color-card__title">{hexLabel}</p>
1922
<p className="color-card__subtitle">{hex}</p>
2023
</div>
2124
</div>
2225
</div>
2326
);
2427

2528
const NEUTRALS = [
26-
{
27-
name: 'Gray 100',
28-
hex: '#f6f9fc',
29-
},
30-
{
31-
name: 'Gray 200',
32-
hex: '#e9ecef',
33-
},
34-
{
35-
name: 'Gray 300',
36-
hex: '#dee2e6',
37-
},
38-
{
39-
name: 'Gray 400',
40-
hex: '#ced4da',
41-
},
42-
{
43-
name: 'Gray 500',
44-
hex: '#adb5bd',
45-
},
46-
{
47-
name: 'Gray 600',
48-
hex: '#8898aa',
49-
},
50-
{
51-
name: 'Gray 700',
52-
hex: '#525f7f',
53-
},
54-
{
55-
name: 'Gray 800',
56-
hex: '#32325d',
57-
},
58-
{
59-
name: 'Gray 900',
60-
hex: '#212529',
61-
},
29+
{ name: 'Gray 100', hex: '#f6f9fc' },
30+
{ name: 'Gray 200', hex: '#e9ecef' },
31+
{ name: 'Gray 300', hex: '#dee2e6' },
32+
{ name: 'Gray 400', hex: '#ced4da' },
33+
{ name: 'Gray 500', hex: '#adb5bd' },
34+
{ name: 'Gray 600', hex: '#8898aa' },
35+
{ name: 'Gray 700', hex: '#525f7f' },
36+
{ name: 'Gray 800', hex: '#32325d' },
37+
{ name: 'Gray 900', hex: '#212529' },
6238
];
6339

6440
const ColorPage = (): React.ReactElement => {
41+
const { siteLocale: s } = useLocaleContext();
42+
6543
return (
6644
<div className="color-page">
67-
<h1 className="markdown__heading-1">Color</h1>
68-
<p className="markdown__p">
69-
Tiny UI uses a specific set of palettes to specify colors to provide a consistent look and
70-
feel for the products you build.
71-
</p>
45+
<h1 className="markdown__heading-1">{s.color.title}</h1>
46+
<p className="markdown__p">{s.color.intro}</p>
7247

73-
<h2 className="markdown__heading-2">Primary colors</h2>
74-
<p className="markdown__p">
75-
Primary palette is comprised of neutrals, white, and purple. These colors are present across
76-
most touch points from marketing to product.
77-
</p>
48+
<h2 className="markdown__heading-2">{s.color.primaryTitle}</h2>
49+
<p className="markdown__p">{s.color.primaryDesc}</p>
7850
<div className="color-page__color-panel">
79-
<ColorCard name="Default" hex="#172b4d" />
80-
<ColorCard name="Primary" hex="#6E41BF" />
81-
<ColorCard name="Secondary" hex="#f7fafc" />
82-
<ColorCard name="Info" hex="#00bcd4" />
83-
<ColorCard name="Success" hex="#4caf50" />
84-
<ColorCard name="Danger" hex="#f44336" />
85-
<ColorCard name="Warning" hex="#ff9800" />
51+
<ColorCard name={s.color.defaultColor} hex="#172b4d" nameLabel={s.color.nameLabel} hexLabel={s.color.hexLabel} />
52+
<ColorCard name={s.color.primary} hex="#6E41BF" nameLabel={s.color.nameLabel} hexLabel={s.color.hexLabel} />
53+
<ColorCard name={s.color.secondary} hex="#f7fafc" nameLabel={s.color.nameLabel} hexLabel={s.color.hexLabel} />
54+
<ColorCard name={s.color.info} hex="#00bcd4" nameLabel={s.color.nameLabel} hexLabel={s.color.hexLabel} />
55+
<ColorCard name={s.color.success} hex="#4caf50" nameLabel={s.color.nameLabel} hexLabel={s.color.hexLabel} />
56+
<ColorCard name={s.color.danger} hex="#f44336" nameLabel={s.color.nameLabel} hexLabel={s.color.hexLabel} />
57+
<ColorCard name={s.color.warning} hex="#ff9800" nameLabel={s.color.nameLabel} hexLabel={s.color.hexLabel} />
8658
</div>
8759

88-
<h2 className="markdown__heading-2">Light neutrals</h2>
89-
<p className="markdown__p">
90-
Light neutrals are helpful for offsetting content in a primarily white layout without losing
91-
warmth and cleanliness, and are therefore often used as a background color for web
92-
components.
93-
</p>
60+
<h2 className="markdown__heading-2">{s.color.neutralsTitle}</h2>
61+
<p className="markdown__p">{s.color.neutralsDesc}</p>
9462

9563
<div className="code-table-container">
9664
<table className="color-page__table">

site/src/containers/components/index.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import React, { Suspense } from 'react';
1+
import React, { Suspense, useMemo } from 'react';
22
import { Route, Routes } from 'react-router-dom';
3-
import { COMPONENT_MENU, RouterItem } from '../../routers';
3+
import { getComponentMenu, RouterItem } from '../../routers';
44
import { SidebarMenu } from '../../components/sidebar-menu';
55
import { Layout, Loader, Divider } from '../../../../components';
66
import { DocFooter } from '../../components/doc-footer';
7+
import { useLocaleContext } from '../../context/locale-context';
78
import ComponentOverview from './overview';
89

910
const { Content } = Layout;
@@ -18,17 +19,20 @@ const flattenRouters = (routers: RouterItem[]): RouterItem[] => {
1819
};
1920

2021
const ComponentsPage = (): React.ReactElement => {
21-
const flattenedRouters = flattenRouters(COMPONENT_MENU);
22+
const { siteLocale } = useLocaleContext();
23+
const componentMenu = useMemo(() => getComponentMenu(siteLocale), [siteLocale]);
24+
const flattenedRouters = useMemo(() => flattenRouters(componentMenu), [componentMenu]);
25+
2226
return (
2327
<Layout className="doc-container">
24-
<SidebarMenu routers={COMPONENT_MENU} url="/components" />
28+
<SidebarMenu routers={componentMenu} url="/components" />
2529
<Layout className="doc-container__layout">
2630
<Content>
2731
<Suspense
2832
fallback={
2933
<div className="doc-container__fallback">
3034
<Loader />
31-
<div style={{ marginLeft: 8 }}>Loading...</div>
35+
<div style={{ marginLeft: 8 }}>{siteLocale.common.loading}</div>
3236
</div>
3337
}>
3438
<Routes>
@@ -37,7 +41,7 @@ const ComponentsPage = (): React.ReactElement => {
3741
const Component = menu.component;
3842
return (
3943
<Route
40-
key={menu.title}
44+
key={menu.route}
4145
path={menu.route}
4246
element={<Component />}
4347
/>

site/src/containers/components/overview.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
import React from 'react';
1+
import React, { useMemo } from 'react';
22
import './component-overview.scss';
33
import { useNavigate } from 'react-router-dom';
44
import { Row, Tag, Button } from '../../../../components';
5-
import { COMPONENT_MENU } from '../../routers';
5+
import { getComponentMenu } from '../../routers';
6+
import { useLocaleContext } from '../../context/locale-context';
67

78
const ComponentOverview = () => {
89
const navigate = useNavigate();
9-
const numOfComps = COMPONENT_MENU.reduce((res, i) => {
10+
const { siteLocale: s } = useLocaleContext();
11+
const componentMenu = useMemo(() => getComponentMenu(s), [s]);
12+
const numOfComps = componentMenu.reduce((res, i) => {
1013
res += i.children!.length;
1114
return res;
1215
}, 0);
1316
return (
1417
<div className="component-overview">
15-
<h1 className="markdown__heading-1">Component Overview</h1>
16-
<p className="markdown__p">
17-
There is an overview of all components. Currently Tiny-UI provides <b>{numOfComps}</b>{' '}
18-
components.
19-
</p>
20-
{COMPONENT_MENU.map((router) => (
18+
<h1 className="markdown__heading-1">{s.overview.title}</h1>
19+
<p className="markdown__p">{s.overview.desc(numOfComps)}</p>
20+
{componentMenu.map((router) => (
2121
<div key={router.title}>
2222
<h3 className="markdown__heading-3">
2323
{router.title} <Tag>{router.children?.length}</Tag>
@@ -26,7 +26,7 @@ const ComponentOverview = () => {
2626
{router.children!.map((item) => (
2727
<Button
2828
className="component-overview__btn"
29-
key={item.title}
29+
key={item.route}
3030
onClick={() => navigate(`/components/${item.route!}`)}>
3131
{item.title}
3232
</Button>

site/src/containers/guide/index.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,45 @@
1-
import React, { Suspense } from 'react';
1+
import React, { Suspense, useMemo } from 'react';
22
import { Route, Routes, Navigate } from 'react-router-dom';
3-
import { GUIDE_MENU } from '../../routers';
3+
import { getGuideMenu } from '../../routers';
44
import { SidebarMenu } from '../../components/sidebar-menu';
55
import { Layout, Loader, Divider } from '../../../../components';
66
import { DocFooter } from '../../components/doc-footer';
7+
import { useLocaleContext } from '../../context/locale-context';
78

89
const { Content } = Layout;
910

1011
const GuidePage = (): React.ReactElement => {
12+
const { siteLocale } = useLocaleContext();
13+
const guideMenu = useMemo(() => getGuideMenu(siteLocale), [siteLocale]);
14+
1115
return (
1216
<Layout className="doc-container">
13-
<SidebarMenu routers={GUIDE_MENU} url="/guide" />
17+
<SidebarMenu routers={guideMenu} url="/guide" />
1418
<Layout className="doc-container__layout">
1519
<Content>
1620
<Suspense
1721
fallback={
1822
<div className="doc-container__fallback">
1923
<Loader />
20-
<div style={{ marginLeft: 8 }}>Loading...</div>
24+
<div style={{ marginLeft: 8 }}>{siteLocale.common.loading}</div>
2125
</div>
2226
}>
2327
<Routes>
24-
{GUIDE_MENU.map((menu) => {
28+
{guideMenu.map((menu) => {
2529
const Component = menu.component;
2630
return (
2731
<Route
28-
key={menu.title}
32+
key={menu.route}
2933
path={menu.route}
3034
element={<Component />}
3135
/>
3236
);
3337
})}
34-
<Route path="" element={<Navigate to={GUIDE_MENU[0].route!} replace />} />
38+
<Route path="" element={<Navigate to={guideMenu[0].route!} replace />} />
3539
</Routes>
3640
</Suspense>
3741
<Divider className="doc-container__divider" />
38-
<DocFooter routers={GUIDE_MENU} />
42+
<DocFooter routers={guideMenu} />
3943
</Content>
4044
</Layout>
4145
</Layout>

0 commit comments

Comments
 (0)