mirror of
https://github.com/reonokiy/blog.nokiy.net.git
synced 2025-06-16 19:51:07 +02:00
✨ feat: complete language switching functionality and centralized page routing
This commit is contained in:
parent
4651828dd1
commit
05d3a8034b
26 changed files with 253 additions and 146 deletions
161
src/i18n/lang.ts
Normal file
161
src/i18n/lang.ts
Normal file
|
@ -0,0 +1,161 @@
|
|||
import themeConfig from '@/config'
|
||||
|
||||
// 从配置中获取默认语言和更多语言配置
|
||||
const defaultLocale = themeConfig.global.locale
|
||||
// const moreLocale = themeConfig.global.moreLocale
|
||||
|
||||
/**
|
||||
* 获取下一个语言代码
|
||||
* @param currentLang 当前语言代码
|
||||
* @returns 下一个语言代码
|
||||
*/
|
||||
export function getNextLang(currentLang: string): string {
|
||||
// 获取默认语言和所有支持的语言
|
||||
const defaultLocale = themeConfig.global.locale
|
||||
const allLocales = [defaultLocale, ...themeConfig.global.moreLocale]
|
||||
|
||||
// 找到当前语言在列表中的索引
|
||||
const currentIndex = allLocales.indexOf(currentLang)
|
||||
|
||||
// 如果当前语言不在列表中,返回默认语言
|
||||
if (currentIndex === -1) {
|
||||
return defaultLocale
|
||||
}
|
||||
|
||||
// 计算下一个语言的索引(循环)
|
||||
const nextIndex = (currentIndex + 1) % allLocales.length
|
||||
|
||||
// 返回下一个语言代码
|
||||
return allLocales[nextIndex]
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建下一个语言的URL
|
||||
* @param currentPath 当前页面路径
|
||||
* @param currentLang 当前语言代码
|
||||
* @param nextLang 下一个语言代码
|
||||
* @returns 下一个语言的URL
|
||||
*/
|
||||
export function buildNextLangUrl(currentPath: string, currentLang: string, nextLang: string): string {
|
||||
const defaultLocale = themeConfig.global.locale
|
||||
let nextUrl = ''
|
||||
|
||||
if (nextLang === defaultLocale) {
|
||||
// 如果下一个是默认语言,移除语言代码
|
||||
nextUrl = currentPath.replace(`/${currentLang}`, '') || '/'
|
||||
}
|
||||
else {
|
||||
// 如果当前是默认语言(没有语言代码在路径中)
|
||||
if (currentLang === defaultLocale) {
|
||||
// 在路径前添加新的语言代码
|
||||
nextUrl = `/${nextLang}${currentPath}`
|
||||
}
|
||||
else {
|
||||
// 替换当前语言代码为新的语言代码
|
||||
nextUrl = currentPath.replace(`/${currentLang}`, `/${nextLang}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 确保URL格式正确
|
||||
if (nextUrl === '')
|
||||
nextUrl = '/'
|
||||
|
||||
// 确保非根路径的URL末尾有斜杠
|
||||
if (nextUrl !== '/' && !nextUrl.endsWith('/')) {
|
||||
nextUrl = `${nextUrl}/`
|
||||
}
|
||||
|
||||
return nextUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前路径中提取语言代码
|
||||
* @param currentPath 当前页面路径
|
||||
* @returns 当前语言代码
|
||||
*/
|
||||
export function getLangFromPath(currentPath: string): string {
|
||||
const defaultLocale = themeConfig.global.locale
|
||||
let currentLang = ''
|
||||
|
||||
// 检查路径是否以/xx/开始,其中xx是支持的语言代码
|
||||
for (const lang of themeConfig.global.moreLocale) {
|
||||
if (currentPath.startsWith(`/${lang}/`) || currentPath === `/${lang}`) {
|
||||
currentLang = lang
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到语言代码,则认为是默认语言
|
||||
if (!currentLang) {
|
||||
currentLang = defaultLocale
|
||||
}
|
||||
|
||||
return currentLang
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章支持的语言
|
||||
* @param lang 文章的语言属性
|
||||
* @returns 支持的语言数组
|
||||
*/
|
||||
export function getSupportedLangs(lang?: string): string[] {
|
||||
const defaultLocale = themeConfig.global.locale
|
||||
const allLocales = [defaultLocale, ...themeConfig.global.moreLocale]
|
||||
|
||||
// 如果指定了语言且不为空
|
||||
if (lang && typeof lang === 'string' && lang.trim() !== '') {
|
||||
return [lang]
|
||||
}
|
||||
|
||||
// 否则返回所有支持的语言
|
||||
return allLocales
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接从当前路径获取下一个语言的URL
|
||||
* @param currentPath 当前页面路径
|
||||
* @returns 下一个语言的URL
|
||||
*/
|
||||
export function getNextLangUrl(currentPath: string): string {
|
||||
// 从路径提取当前语言
|
||||
const currentLang = getLangFromPath(currentPath)
|
||||
|
||||
// 获取下一个语言
|
||||
const nextLang = getNextLang(currentLang)
|
||||
|
||||
// 构建下一个语言的URL
|
||||
return buildNextLangUrl(currentPath, currentLang, nextLang)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据支持的语言列表获取下一个语言的URL
|
||||
* @param currentPath 当前路径
|
||||
* @param supportedLangs 文章支持的语言列表
|
||||
* @returns 下一个可用语言的URL
|
||||
*/
|
||||
export function getPostNextLangUrl(currentPath: string, supportedLangs: string[]): string {
|
||||
// 从路径提取当前语言
|
||||
const currentLang = getLangFromPath(currentPath)
|
||||
|
||||
// 如果没有提供支持的语言或列表为空,使用普通的语言切换
|
||||
if (!supportedLangs || supportedLangs.length === 0) {
|
||||
return getNextLangUrl(currentPath)
|
||||
}
|
||||
|
||||
// 找到当前语言在支持的语言中的索引
|
||||
const currentIndex = supportedLangs.indexOf(currentLang)
|
||||
|
||||
// 如果当前语言不在支持的语言中,或者路径是根路径,返回第一个支持的语言
|
||||
if (currentIndex === -1 || currentPath === '/') {
|
||||
const nextLang = supportedLangs[0]
|
||||
// 如果下一个语言是默认语言,返回根路径
|
||||
return nextLang === defaultLocale ? '/' : `/${nextLang}/`
|
||||
}
|
||||
|
||||
// 计算下一个语言的索引
|
||||
const nextIndex = (currentIndex + 1) % supportedLangs.length
|
||||
const nextLang = supportedLangs[nextIndex]
|
||||
|
||||
// 构建下一个语言的URL
|
||||
return buildNextLangUrl(currentPath, currentLang, nextLang)
|
||||
}
|
60
src/i18n/path.ts
Normal file
60
src/i18n/path.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import themeConfig from '@/config'
|
||||
|
||||
const defaultLocale = themeConfig.global.locale
|
||||
const moreLocales = themeConfig.global.moreLocale
|
||||
|
||||
export function cleanPath(path: string) {
|
||||
return path.replace(/^\/|\/$/g, '')
|
||||
}
|
||||
|
||||
export function getLangFromPath(path: string) {
|
||||
const secondaryLang = moreLocales.find(
|
||||
lang =>
|
||||
path.startsWith(`/${lang}/`),
|
||||
)
|
||||
return secondaryLang || defaultLocale
|
||||
}
|
||||
|
||||
export function getLocalizedPath(path: string, currentLang?: string) {
|
||||
const clean = cleanPath(path)
|
||||
const lang = currentLang || getLangFromPath(path)
|
||||
|
||||
if (clean === '') {
|
||||
return lang === defaultLocale ? '/' : `/${lang}/`
|
||||
}
|
||||
|
||||
return lang === defaultLocale ? `/${clean}/` : `/${lang}/${clean}/`
|
||||
}
|
||||
|
||||
export function isHomePage(path: string) {
|
||||
const clean = cleanPath(path)
|
||||
return clean === '' || moreLocales.includes(clean)
|
||||
}
|
||||
|
||||
export function isPostPage(path: string) {
|
||||
const clean = cleanPath(path)
|
||||
return clean.startsWith('posts') || moreLocales.some(lang => clean.startsWith(`${lang}/posts`))
|
||||
}
|
||||
|
||||
export function isTagPage(path: string) {
|
||||
const clean = cleanPath(path)
|
||||
return clean.startsWith('tags') || moreLocales.some(lang => clean.startsWith(`${lang}/tags`))
|
||||
}
|
||||
|
||||
export function isAboutPage(path: string) {
|
||||
const clean = cleanPath(path)
|
||||
return clean.startsWith('about') || moreLocales.some(lang => clean.startsWith(`${lang}/about`))
|
||||
}
|
||||
|
||||
export function getPagePath(path: string) {
|
||||
const currentLang = getLangFromPath(path)
|
||||
|
||||
return {
|
||||
currentLang,
|
||||
isHome: isHomePage(path),
|
||||
isPost: isPostPage(path),
|
||||
isTag: isTagPage(path),
|
||||
isAbout: isAboutPage(path),
|
||||
getLocalizedPath: (targetPath: string) => getLocalizedPath(targetPath, currentLang),
|
||||
}
|
||||
}
|
131
src/i18n/route.ts
Normal file
131
src/i18n/route.ts
Normal file
|
@ -0,0 +1,131 @@
|
|||
import type { CollectionEntry } from 'astro:content'
|
||||
import { themeConfig } from '@/config'
|
||||
|
||||
// 默认语言和更多语言
|
||||
const defaultLocale = themeConfig.global.locale
|
||||
const moreLocale = themeConfig.global.moreLocale
|
||||
// 所有支持的语言
|
||||
const allLocales = [defaultLocale, ...moreLocale]
|
||||
|
||||
// 生成默认语言标签页面的路径配置
|
||||
export function generateTagPaths(tags: string[]) {
|
||||
return tags.map(tag => ({
|
||||
params: { tag },
|
||||
props: { tag },
|
||||
}))
|
||||
}
|
||||
|
||||
// 生成默认语言文章页面的路径配置
|
||||
export function generatePostPaths(posts: CollectionEntry<'posts'>[]) {
|
||||
// 创建slug到语言的映射
|
||||
const slugToLangs: Record<string, string[]> = {}
|
||||
|
||||
// 填充映射
|
||||
posts.forEach((post) => {
|
||||
const slug = post.data.abbrlink || post.slug
|
||||
const lang = post.data.lang || defaultLocale
|
||||
|
||||
// 如果文章没有指定语言,初始化为所有支持的语言
|
||||
if (!slugToLangs[slug]) {
|
||||
if (!post.data.lang) {
|
||||
slugToLangs[slug] = [...allLocales] // 文章支持所有语言
|
||||
}
|
||||
else {
|
||||
slugToLangs[slug] = [defaultLocale] // 仅默认语言和指定语言
|
||||
}
|
||||
}
|
||||
|
||||
if (!slugToLangs[slug].includes(lang)) {
|
||||
slugToLangs[slug].push(lang)
|
||||
}
|
||||
})
|
||||
|
||||
return posts.map(post => ({
|
||||
params: {
|
||||
slug: post.data.abbrlink || post.slug,
|
||||
},
|
||||
props: {
|
||||
post,
|
||||
supportedLangs: slugToLangs[post.data.abbrlink || post.slug] || [],
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
// 生成更多语言静态路径
|
||||
export function generateMultiLangPaths() {
|
||||
return moreLocale.map(lang => ({
|
||||
params: { lang },
|
||||
}))
|
||||
}
|
||||
|
||||
// 生成更多语言标签页面的路径配置
|
||||
export function generateMultiLangTagPaths(tags: string[]) {
|
||||
return moreLocale.flatMap(lang => (
|
||||
tags.map(tag => ({
|
||||
params: { lang, tag },
|
||||
props: { tag },
|
||||
}))
|
||||
))
|
||||
}
|
||||
|
||||
// 生成更多语言文章页面的路径配置
|
||||
export function generateMultiLangPostPaths(posts: CollectionEntry<'posts'>[]) {
|
||||
// 创建slug到语言的映射
|
||||
const slugToLangs: Record<string, string[]> = {}
|
||||
|
||||
// 填充映射
|
||||
posts.forEach((post) => {
|
||||
const slug = post.data.abbrlink || post.slug
|
||||
const lang = post.data.lang || defaultLocale
|
||||
|
||||
// 如果文章没有指定语言,初始化为所有支持的语言
|
||||
if (!slugToLangs[slug]) {
|
||||
if (!post.data.lang) {
|
||||
slugToLangs[slug] = [...allLocales] // 文章支持所有语言
|
||||
}
|
||||
else {
|
||||
slugToLangs[slug] = [defaultLocale] // 仅默认语言和指定语言
|
||||
}
|
||||
}
|
||||
|
||||
if (!slugToLangs[slug].includes(lang)) {
|
||||
slugToLangs[slug].push(lang)
|
||||
}
|
||||
})
|
||||
|
||||
interface PathResult {
|
||||
params: {
|
||||
lang: string
|
||||
slug: string
|
||||
}
|
||||
props: {
|
||||
post: CollectionEntry<'posts'>
|
||||
supportedLangs: string[]
|
||||
}
|
||||
}
|
||||
|
||||
return posts.flatMap((post) => {
|
||||
const result: PathResult[] = []
|
||||
const slug = post.data.abbrlink || post.slug
|
||||
|
||||
// 确定文章的语言支持
|
||||
const postLang = post.data.lang && typeof post.data.lang === 'string' && post.data.lang.trim() !== ''
|
||||
? [post.data.lang]
|
||||
: moreLocale
|
||||
|
||||
// 获取这篇文章支持的所有语言
|
||||
const supportedLangs = slugToLangs[slug] || []
|
||||
|
||||
// 添加非默认语言路径
|
||||
postLang.forEach((lang) => {
|
||||
if (lang !== defaultLocale) {
|
||||
result.push({
|
||||
params: { lang, slug },
|
||||
props: { post, supportedLangs },
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
76
src/i18n/ui.ts
Normal file
76
src/i18n/ui.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
// Global Language Map
|
||||
export const langMap: Record<string, string[]> = {
|
||||
'zh': ['zh-CN'],
|
||||
'zh-tw': ['zh-TW'],
|
||||
'ja': ['ja-JP'],
|
||||
'en': ['en-US'],
|
||||
'es': ['es-ES'],
|
||||
'ru': ['ru-RU'],
|
||||
}
|
||||
|
||||
// Standard Language Code (Unused)
|
||||
export const langCode = Object.values(langMap).flat()
|
||||
|
||||
// Abbreviated Language Code
|
||||
export const langPath = Object.keys(langMap).flat()
|
||||
|
||||
// UI Translation
|
||||
export const ui = {
|
||||
'zh': {
|
||||
posts: '文章',
|
||||
tags: '标签',
|
||||
about: '关于',
|
||||
},
|
||||
'zh-tw': {
|
||||
posts: '文章',
|
||||
tags: '標籤',
|
||||
about: '關於',
|
||||
},
|
||||
'ja': {
|
||||
posts: '記事',
|
||||
tags: 'タグ',
|
||||
about: '概要',
|
||||
},
|
||||
'en': {
|
||||
posts: 'Posts',
|
||||
tags: 'Tags',
|
||||
about: 'About',
|
||||
},
|
||||
'es': {
|
||||
posts: 'Artículos',
|
||||
tags: 'Etiquetas',
|
||||
about: 'Sobre',
|
||||
},
|
||||
'ru': {
|
||||
posts: 'Посты',
|
||||
tags: 'Теги',
|
||||
about: 'О себе',
|
||||
},
|
||||
}
|
||||
|
||||
// Waline Language Map
|
||||
// See more at https://waline.js.org/guide/i18n.html
|
||||
export const walineLocaleMap: Record<string, string> = {
|
||||
'zh': 'zh-CN',
|
||||
'zh-tw': 'zh-TW',
|
||||
'ja': 'jp-JP', // Waline uses jp-JP not ja-JP
|
||||
'en': 'en-US',
|
||||
'es': 'es-ES',
|
||||
'ru': 'ru-RU',
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the language code of Waline
|
||||
* @param currentPath Current page path
|
||||
* @param defaultLocale Default language
|
||||
* @returns Corresponding Waline language code
|
||||
*/
|
||||
export function getWalineLang(currentPath: string, defaultLocale: string): string {
|
||||
// Extract language code from path
|
||||
const pathLang = Object.keys(walineLocaleMap).find(code =>
|
||||
currentPath.startsWith(`/${code}/`),
|
||||
)
|
||||
// Return found path language or default language
|
||||
const lang = pathLang || defaultLocale
|
||||
return walineLocaleMap[lang as keyof typeof walineLocaleMap]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue