feat: migrate language code
All checks were successful
Deploy / Deploy (push) Successful in 1m15s

This commit is contained in:
CDN 2025-02-03 22:52:56 +08:00
parent 47cf6171c5
commit 9ac43ef4f9
Signed by: CDN
GPG key ID: 0C656827F9F80080
86 changed files with 119 additions and 80 deletions

View file

@ -46,7 +46,7 @@ homepage
## Contribution Guide ## Contribution Guide
See [CONTRIBUTING.md](docs/en-US/CONTRIBUTING.md) in the `docs` directory to learn how to participate in the project. See [CONTRIBUTING.md](docs/en/CONTRIBUTING.md) in the `docs` directory to learn how to participate in the project.
## License ## License

View file

@ -46,7 +46,7 @@ homepage
## 贡献指南 ## 贡献指南
参见 `docs` 目录中的 [CONTRIBUTING.md](docs/zh-CN/CONTRIBUTING.md) 了解如何参与项目。 参见 `docs` 目录中的 [CONTRIBUTING.md](docs/zh-Hans/CONTRIBUTING.md) 了解如何参与项目。
## 许可 ## 许可

View file

@ -46,7 +46,7 @@ homepage
## 貢獻指南 ## 貢獻指南
參見 `docs` 目錄中的 [CONTRIBUTING.md](docs/zh-TW/CONTRIBUTING.md) 了解如何參與專案。 參見 `docs` 目錄中的 [CONTRIBUTING.md](docs/zh-Hant/CONTRIBUTING.md) 了解如何參與專案。
## 許可 ## 許可

View file

@ -1,3 +1,3 @@
- [简体中文](./zh-CN/) - [简体中文](./zh-Hans/)
- [繁體中文](./zh-TW/) - [繁體中文](./zh-Hant/)
- [English](./en-US/) - [English](./en/)

View file

@ -14,22 +14,22 @@ interface LanguageConfig {
const LANGUAGES: LanguageConfig[] = [ const LANGUAGES: LanguageConfig[] = [
{ {
code: 'en-US', code: 'en',
dataDir: 'en-US', dataDir: 'en',
title: 'STARSET Mirror Site Updates', title: 'Starset Mirror - Updates',
description: 'Latest updates from STARSET Mirror Site' description: 'Latest updates from Starset Mirror'
}, },
{ {
code: 'zh-CN', code: 'zh-Hans',
dataDir: 'zh-CN', dataDir: 'zh-Hans',
title: 'STARSET Mirror 项目动态', title: 'Starset Mirror - 更新',
description: 'STARSET Mirror 最新动态' description: 'Starset Mirror 的最新更新'
}, },
{ {
code: 'zh-Hant', code: 'zh-Hant',
dataDir: 'zh-TW', dataDir: 'zh-Hant',
title: 'STARSET Mirror 專案動态', title: 'Starset Mirror - 更新',
description: 'STARSET Mirror 最新動態' description: 'Starset Mirror 的最新更新'
} }
]; ];

View file

@ -6,7 +6,7 @@ import xml2js from 'xml2js';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
const LANGUAGES = ['en-US', 'zh-CN', 'zh-TW']; const LANGUAGES = ['en', 'zh-Hans', 'zh-Hant'];
const BASE_URL = 'mirror.starset.fans'; const BASE_URL = 'mirror.starset.fans';
interface Update { interface Update {

View file

@ -6,7 +6,7 @@ import xml2js from 'xml2js';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
const LANGUAGES = ['en-US', 'zh-CN', 'zh-TW']; const LANGUAGES = ['en', 'zh-Hans', 'zh-Hant'];
const BASE_URL = 'starset.wiki'; // Replace with your actual domain const BASE_URL = 'starset.wiki'; // Replace with your actual domain
async function getYearlyIndices(lang) { async function getYearlyIndices(lang) {

View file

@ -4,22 +4,24 @@ import iconMap from '../utils/iconMap';
const About = () => { const About = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const aboutData = t('data.about', { returnObjects: true }); const aboutData = t('data.about', { returnObjects: true }) || {};
const socialLinks = t('social.links', { returnObjects: true }); const socialLinks = t('social.links', { returnObjects: true }) || [];
if (!aboutData) return null;
return ( return (
<div className="container mx-auto px-2 md:px-4"> <div className="container mx-auto px-2 md:px-4">
<div className="max-w-3xl mx-auto"> <div className="max-w-3xl mx-auto">
<h2 className="text-3xl font-bold text-center mb-8 text-gray-900 dark:text-white">{t('about.title')}</h2> <h2 className="text-3xl font-bold text-center mb-8 text-gray-900 dark:text-white">{t('about.title')}</h2>
<div className="prose prose-lg mx-auto dark:prose-invert"> <div className="prose prose-lg mx-auto dark:prose-invert">
{aboutData.content.intro.map((paragraph: string, index: number) => ( {aboutData.content?.intro?.map((paragraph: string, index: number) => (
<p key={index} className="text-gray-600 dark:text-gray-300 mb-6"> <p key={index} className="text-gray-600 dark:text-gray-300 mb-6">
{paragraph} {paragraph}
</p> </p>
))} ))}
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mt-12"> <div className="grid grid-cols-1 md:grid-cols-3 gap-8 mt-12">
{aboutData.stats.map((stat: { value: string; label: string }, index: number) => ( {aboutData.stats?.map((stat: { value: string; label: string }, index: number) => (
<div key={index} className="text-center"> <div key={index} className="text-center">
<h3 className="text-4xl font-bold text-blue-600 dark:text-blue-400 mb-2">{stat.value}</h3> <h3 className="text-4xl font-bold text-blue-600 dark:text-blue-400 mb-2">{stat.value}</h3>
<p className="text-gray-600 dark:text-gray-300">{stat.label}</p> <p className="text-gray-600 dark:text-gray-300">{stat.label}</p>
@ -27,7 +29,7 @@ const About = () => {
))} ))}
</div> </div>
{aboutData.content.workScope && ( {aboutData.content?.workScope && (
<div className="mt-12"> <div className="mt-12">
<h3 className="text-2xl font-semibold mb-6 text-gray-900 dark:text-white"></h3> <h3 className="text-2xl font-semibold mb-6 text-gray-900 dark:text-white"></h3>
<ol className="space-y-4"> <ol className="space-y-4">
@ -36,7 +38,7 @@ const About = () => {
<span className="flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-400 font-semibold"> <span className="flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-400 font-semibold">
{index + 1} {index + 1}
</span> </span>
<span className="text-lg">{item}</span> <span>{item}</span>
</li> </li>
))} ))}
</ol> </ol>
@ -67,14 +69,14 @@ const About = () => {
<div className="mt-12"> <div className="mt-12">
<h3 className="text-2xl font-semibold mb-4 text-gray-900 dark:text-white">{t('about.join.title')}</h3> <h3 className="text-2xl font-semibold mb-4 text-gray-900 dark:text-white">{t('about.join.title')}</h3>
<p className="text-gray-600 dark:text-gray-300"> <p className="text-gray-600 dark:text-gray-300">
{aboutData.content.contact.description} {aboutData.content?.contact?.description}
</p> </p>
<div className="mt-4"> <div className="mt-4">
<a <a
href={`mailto:${aboutData.content.contact.email}`} href={`mailto:${aboutData.content?.contact?.email}`}
className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 no-underline" className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 no-underline"
> >
{aboutData.content.contact.email} {aboutData.content?.contact?.email}
</a> </a>
</div> </div>
</div> </div>

View file

@ -1,5 +1,6 @@
import { useCallback, useRef, useEffect } from 'react' import { useCallback, useRef, useEffect } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import 'artalk/dist/Artalk.css' import 'artalk/dist/Artalk.css'
import Artalk from 'artalk' import Artalk from 'artalk'
@ -9,6 +10,7 @@ interface CommentsProps {
const Comments = ({ title }: CommentsProps) => { const Comments = ({ title }: CommentsProps) => {
const { pathname } = useLocation() const { pathname } = useLocation()
const { t } = useTranslation()
const artalkRef = useRef<Artalk>() const artalkRef = useRef<Artalk>()
const containerRef = useRef<HTMLDivElement>(null) const containerRef = useRef<HTMLDivElement>(null)

View file

@ -23,7 +23,8 @@ interface Contributor {
const Contributors: React.FC = () => { const Contributors: React.FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const members = t('data.contributors.members', { returnObjects: true }) as Contributor[]; const contributorsData = t('data.contributors', { returnObjects: true }) || {};
const members = Array.isArray(contributorsData.members) ? contributorsData.members : [];
// Fisher-Yates shuffle algorithm // Fisher-Yates shuffle algorithm
const shuffleArray = <T,>(array: T[]): T[] => { const shuffleArray = <T,>(array: T[]): T[] => {

View file

@ -8,9 +8,9 @@ const LanguageSwitcher = () => {
const menuRef = useRef<HTMLDivElement>(null); const menuRef = useRef<HTMLDivElement>(null);
const languages = [ const languages = [
{ code: 'zh-CN', name: '简体中文' }, { code: 'zh-Hans', name: '简体中文' },
{ code: 'zh-TW', name: '繁體中文' }, { code: 'zh-Hant', name: '繁體中文' },
{ code: 'en-US', name: 'English' } { code: 'en', name: 'English' }
]; ];
useEffect(() => { useEffect(() => {

View file

@ -13,18 +13,19 @@ interface Project {
const Projects = () => { const Projects = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const projects = t('data.projects.projects', { returnObjects: true }) as Project[]; const projectsData = t('data.projects', { returnObjects: true }) || {};
const projects = Array.isArray(projectsData.projects) ? projectsData.projects : [];
const [selectedTags, setSelectedTags] = useState<string[]>([]); const [selectedTags, setSelectedTags] = useState<string[]>([]);
// 获取所有可用的标签 // 获取所有可用的标签
const allTags = Array.from( const allTags = Array.from(
new Set(projects.flatMap(project => project.tags)) new Set(projects.flatMap(project => project.tags || []))
); );
// 根据选中的标签筛选项目 // 根据选中的标签筛选项目
const filteredProjects = selectedTags.length > 0 const filteredProjects = selectedTags.length > 0
? projects.filter(project => ? projects.filter(project =>
selectedTags.every(tag => project.tags.includes(tag)) selectedTags.every(tag => (project.tags || []).includes(tag))
) )
: projects; : projects;
@ -119,7 +120,7 @@ const Projects = () => {
{/* 标签区域固定高度 */} {/* 标签区域固定高度 */}
<div className="h-8 mb-4 flex items-center overflow-x-auto"> <div className="h-8 mb-4 flex items-center overflow-x-auto">
<div className="flex gap-2"> <div className="flex gap-2">
{project.tags.map((tag) => ( {(project.tags || []).map((tag) => (
<button <button
key={tag} key={tag}
onClick={() => handleTagClick(tag)} onClick={() => handleTagClick(tag)}

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Card, Avatar, Tag } from 'antd'; import { Card, Avatar, Tag } from 'antd';
import { GithubOutlined, TwitterOutlined } from '@ant-design/icons'; import { GithubOutlined, TwitterOutlined } from '@ant-design/icons';
import sponsorsData from '../../data/zh-CN/sponsors.json'; import sponsorsData from '../../data/zh-Hans/sponsors.json';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
interface SponsorType { interface SponsorType {
@ -13,26 +13,33 @@ interface SponsorType {
github?: string; github?: string;
twitter?: string; twitter?: string;
}; };
name: string;
name_zh_TW?: string;
name_en?: string;
} }
const Sponsors: React.FC = () => { const Sponsors: React.FC = () => {
const { i18n } = useTranslation(); const { i18n } = useTranslation();
const getMessage = () => { const getMessage = (sponsor: SponsorType) => {
switch (i18n.language) { switch (i18n.language) {
case 'zh-Hans':
case 'zh-CN': case 'zh-CN':
return '赞助人信息仍在同步,将在晚些时候上线。'; return sponsor.name;
case 'zh-Hant':
case 'zh-TW': case 'zh-TW':
return '贊助人資訊仍在同步,將在晚些時候上線。'; return sponsor.name_zh_TW || sponsor.name;
case 'en':
case 'en-US': case 'en-US':
return sponsor.name_en || sponsor.name;
default: default:
return 'Sponsor information is still being synchronized and will be available later.'; return sponsor.name;
} }
}; };
return ( return (
<div className="flex items-center justify-center min-h-[300px] text-gray-600 dark:text-gray-300 text-lg"> <div className="flex items-center justify-center min-h-[300px] text-gray-600 dark:text-gray-300 text-lg">
{getMessage()} {getMessage(sponsorsData[0])}
</div> </div>
); );
@ -51,7 +58,7 @@ const Sponsors: React.FC = () => {
/> />
</div> </div>
)} )}
<h3 className="text-lg font-semibold mb-3 text-gray-900 dark:text-white">{sponsor.nickname}</h3> <h3 className="text-lg font-semibold mb-3 text-gray-900 dark:text-white">{getMessage(sponsor)}</h3>
<div className="space-y-4"> <div className="space-y-4">
<div className="flex justify-center gap-3"> <div className="flex justify-center gap-3">
<Tag color="gold">{sponsor.year}</Tag> <Tag color="gold">{sponsor.year}</Tag>

View file

@ -62,6 +62,7 @@ const Pagination: React.FC<PaginationProps> = ({
const Timeline = () => { const Timeline = () => {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const LANGUAGE_CODE_MAP: Record<string, string> = { const LANGUAGE_CODE_MAP: Record<string, string> = {
'zh-CN': 'zh-Hans',
'zh-TW': 'zh-Hant' 'zh-TW': 'zh-Hant'
}; };
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();

View file

@ -36,10 +36,33 @@ const TranslationBackground = () => {
setIsVisible(true); setIsVisible(true);
}, []); }, []);
const getLanguageName = (language: string) => {
switch (language) {
case 'zh-Hans':
return '简体中文';
case 'zh-Hant':
return '繁體中文';
default:
return 'English';
}
};
const getTranslationName = (language: string) => {
switch (language) {
case 'zh-Hans':
case 'zh-Hant':
return '翻译';
default:
return 'Translation';
}
};
const getMainText = (translation: typeof translations.translations[0]) => { const getMainText = (translation: typeof translations.translations[0]) => {
switch (i18n.language) { switch (i18n.language) {
case 'zh-Hans':
case 'zh-CN': case 'zh-CN':
return translation.zh_CN; return translation.zh_CN;
case 'zh-Hant':
case 'zh-TW': case 'zh-TW':
return translation.zh_TW; return translation.zh_TW;
default: default:
@ -49,7 +72,9 @@ const TranslationBackground = () => {
const getSecondaryText = (translation: typeof translations.translations[0]) => { const getSecondaryText = (translation: typeof translations.translations[0]) => {
switch (i18n.language) { switch (i18n.language) {
case 'zh-Hans':
case 'zh-CN': case 'zh-CN':
case 'zh-Hant':
case 'zh-TW': case 'zh-TW':
return translation.en; return translation.en;
default: default:

View file

@ -3,42 +3,42 @@ import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector'; import LanguageDetector from 'i18next-browser-languagedetector';
// 导入翻译文件 // 导入翻译文件
import zhCNTranslation from '../../data/zh-CN/index.json'; import zhHansTranslation from '../../data/zh-Hans/index.json';
import zhTWTranslation from '../../data/zh-TW/index.json'; import zhHantTranslation from '../../data/zh-Hant/index.json';
import enUSTranslation from '../../data/en-US/index.json'; import enTranslation from '../../data/en/index.json';
// 导入数据文件 // 导入数据文件
import zhCNAbout from '../../data/zh-CN/about.json'; import zhHansAbout from '../../data/zh-Hans/about.json';
import zhCNProjects from '../../data/zh-CN/projects.json'; import zhHansProjects from '../../data/zh-Hans/projects.json';
import zhCNContributors from '../../data/zh-CN/contributors.json'; import zhHansContributors from '../../data/zh-Hans/contributors.json';
import zhCNUpdates from '../../data/zh-CN/updates.json'; import zhHansUpdates from '../../data/zh-Hans/updates.json';
import zhTWAbout from '../../data/zh-TW/about.json'; import zhHantAbout from '../../data/zh-Hant/about.json';
import zhTWProjects from '../../data/zh-TW/projects.json'; import zhHantProjects from '../../data/zh-Hant/projects.json';
import zhTWContributors from '../../data/zh-TW/contributors.json'; import zhHantContributors from '../../data/zh-Hant/contributors.json';
import zhTWUpdates from '../../data/zh-TW/updates.json'; import zhHantUpdates from '../../data/zh-Hant/updates.json';
import enUSAbout from '../../data/en-US/about.json'; import enAbout from '../../data/en/about.json';
import enUSProjects from '../../data/en-US/projects.json'; import enProjects from '../../data/en/projects.json';
import enUSContributors from '../../data/en-US/contributors.json'; import enContributors from '../../data/en/contributors.json';
import enUSUpdates from '../../data/en-US/updates.json'; import enUpdates from '../../data/en/updates.json';
i18n i18n
.use(LanguageDetector) .use(LanguageDetector)
.use(initReactI18next) .use(initReactI18next)
.init({ .init({
resources: { resources: {
'zh-CN': { 'zh-Hans': {
translation: zhCNTranslation.translation translation: zhHansTranslation.translation
}, },
'zh-TW': { 'zh-Hant': {
translation: zhTWTranslation.translation translation: zhHantTranslation.translation
}, },
'en-US': { 'en': {
translation: enUSTranslation.translation translation: enTranslation.translation
} }
}, },
fallbackLng: 'zh-CN', fallbackLng: 'zh-Hans',
interpolation: { interpolation: {
escapeValue: false, escapeValue: false,
}, },
@ -48,30 +48,30 @@ i18n
}); });
// 添加数据命名空间 // 添加数据命名空间
i18n.addResourceBundle('zh-CN', 'translation', { i18n.addResourceBundle('zh-Hans', 'translation', {
data: { data: {
about: zhCNAbout, about: zhHansAbout,
projects: zhCNProjects, projects: zhHansProjects,
contributors: zhCNContributors, contributors: zhHansContributors,
updates: zhCNUpdates updates: zhHansUpdates
} }
}, true, true); }, true, true);
i18n.addResourceBundle('zh-TW', 'translation', { i18n.addResourceBundle('zh-Hant', 'translation', {
data: { data: {
about: zhTWAbout, about: zhHantAbout,
projects: zhTWProjects, projects: zhHantProjects,
contributors: zhTWContributors, contributors: zhHantContributors,
updates: zhTWUpdates updates: zhHantUpdates
} }
}, true, true); }, true, true);
i18n.addResourceBundle('en-US', 'translation', { i18n.addResourceBundle('en', 'translation', {
data: { data: {
about: enUSAbout, about: enAbout,
projects: enUSProjects, projects: enProjects,
contributors: enUSContributors, contributors: enContributors,
updates: enUSUpdates updates: enUpdates
} }
}, true, true); }, true, true);