update: unified multilingual page styles

This commit is contained in:
radishzzz 2025-03-14 05:20:59 +00:00
parent 05d3a8034b
commit 91d27dd2ba
14 changed files with 118 additions and 85 deletions

View file

@ -1,6 +1,6 @@
---
import { themeConfig } from '@/config'
import { getWalineLang } from '@/i18n/ui'
import { getWalineLang } from '@/i18n/lang'
const {
serverURL = '',

View file

@ -54,7 +54,7 @@ export const themeConfig: ThemeConfig = {
locale: 'zh', // zh, zh-tw, ja, en, es, ru
// more languages
// not fill in the locale above again
moreLocale: ['zh-tw', 'ja', 'en', 'es', 'ru'], // ['zh', 'zh-tw', 'ja', 'en', 'es', 'ru']
moreLocales: ['zh-tw', 'ja', 'en', 'es', 'ru'], // ['zh', 'zh-tw', 'ja', 'en', 'es', 'ru']
// font styles for text
fontStyle: 'sans', // sans, serif
// date format for posts

5
src/i18n/config.ts Normal file
View file

@ -0,0 +1,5 @@
import { themeConfig } from '@/config'
export const defaultLocale = themeConfig.global.locale
export const moreLocales = themeConfig.global.moreLocales
export const allLocales = [defaultLocale, ...moreLocales]

View file

@ -1,8 +1,5 @@
import themeConfig from '@/config'
// 从配置中获取默认语言和更多语言配置
const defaultLocale = themeConfig.global.locale
// const moreLocale = themeConfig.global.moreLocale
import { allLocales, defaultLocale, moreLocales } from '@/i18n/config'
import { walineLocaleMap } from '@/i18n/ui'
/**
*
@ -11,8 +8,7 @@ const defaultLocale = themeConfig.global.locale
*/
export function getNextLang(currentLang: string): string {
// 获取默认语言和所有支持的语言
const defaultLocale = themeConfig.global.locale
const allLocales = [defaultLocale, ...themeConfig.global.moreLocale]
// 直接使用导入的变量
// 找到当前语言在列表中的索引
const currentIndex = allLocales.indexOf(currentLang)
@ -37,7 +33,7 @@ export function getNextLang(currentLang: string): string {
* @returns URL
*/
export function buildNextLangUrl(currentPath: string, currentLang: string, nextLang: string): string {
const defaultLocale = themeConfig.global.locale
// 直接使用导入的变量
let nextUrl = ''
if (nextLang === defaultLocale) {
@ -74,11 +70,11 @@ export function buildNextLangUrl(currentPath: string, currentLang: string, nextL
* @returns
*/
export function getLangFromPath(currentPath: string): string {
const defaultLocale = themeConfig.global.locale
// 直接使用导入的变量
let currentLang = ''
// 检查路径是否以/xx/开始其中xx是支持的语言代码
for (const lang of themeConfig.global.moreLocale) {
for (const lang of moreLocales) {
if (currentPath.startsWith(`/${lang}/`) || currentPath === `/${lang}`) {
currentLang = lang
break
@ -99,8 +95,7 @@ export function getLangFromPath(currentPath: string): string {
* @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() !== '') {
@ -159,3 +154,19 @@ export function getPostNextLangUrl(currentPath: string, supportedLangs: string[]
// 构建下一个语言的URL
return buildNextLangUrl(currentPath, currentLang, nextLang)
}
/**
* 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]
}

View file

@ -1,7 +1,4 @@
import themeConfig from '@/config'
const defaultLocale = themeConfig.global.locale
const moreLocales = themeConfig.global.moreLocale
import { defaultLocale, moreLocales } from '@/i18n/config'
export function cleanPath(path: string) {
return path.replace(/^\/|\/$/g, '')

View file

@ -1,11 +1,5 @@
import type { CollectionEntry } from 'astro:content'
import { themeConfig } from '@/config'
// 默认语言和更多语言
const defaultLocale = themeConfig.global.locale
const moreLocale = themeConfig.global.moreLocale
// 所有支持的语言
const allLocales = [defaultLocale, ...moreLocale]
import { allLocales, defaultLocale, moreLocales } from '@/i18n/config'
// 生成默认语言标签页面的路径配置
export function generateTagPaths(tags: string[]) {
@ -53,14 +47,14 @@ export function generatePostPaths(posts: CollectionEntry<'posts'>[]) {
// 生成更多语言静态路径
export function generateMultiLangPaths() {
return moreLocale.map(lang => ({
return moreLocales.map(lang => ({
params: { lang },
}))
}
// 生成更多语言标签页面的路径配置
export function generateMultiLangTagPaths(tags: string[]) {
return moreLocale.flatMap(lang => (
return moreLocales.flatMap(lang => (
tags.map(tag => ({
params: { lang, tag },
props: { tag },
@ -111,7 +105,7 @@ export function generateMultiLangPostPaths(posts: CollectionEntry<'posts'>[]) {
// 确定文章的语言支持
const postLang = post.data.lang && typeof post.data.lang === 'string' && post.data.lang.trim() !== ''
? [post.data.lang]
: moreLocale
: moreLocales
// 获取这篇文章支持的所有语言
const supportedLangs = slugToLangs[slug] || []

View file

@ -8,6 +8,17 @@ export const langMap: Record<string, string[]> = {
'ru': ['ru-RU'],
}
// Waline Language Map
// docs: 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',
}
// Standard Language Code (Unused)
export const langCode = Object.values(langMap).flat()
@ -47,30 +58,3 @@ export const ui = {
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]
}

View file

@ -1,5 +1,6 @@
---
import themeConfig from '@/config'
import { allLocales, defaultLocale } from '@/i18n/config'
import { ClientRouter } from 'astro:transitions'
interface Props {
@ -12,7 +13,7 @@ const { postTitle, postDescription, postSlug } = Astro.props
const { title, subtitle, description, author, url, favicon } = themeConfig.site
const { mode, light: { background: lightMode }, dark: { background: darkMode } } = themeConfig.color
const { locale, moreLocale } = themeConfig.global
// const { locale, moreLocales } = themeConfig.global
const { verification = {}, twitterID = '', googleAnalyticsID = '', umamiAnalyticsID = '' } = themeConfig.seo ?? {}
const { google = '', bing = '', yandex = '', baidu = '' } = verification
const { commentURL = '', imageHostURL = '', customGoogleAnalyticsJS = '', customUmamiAnalyticsJS = '' } = themeConfig.preload
@ -48,10 +49,10 @@ const pageImage = postSlug ? `${url}/og/${postSlug}.png` : 'https://placehold.co
<link rel="canonical" href={Astro.url} />
<!-- i18n hreflang generate -->
{[locale, ...moreLocale].map(lang => (
{allLocales.map(lang => (
<link
rel="alternate"
href={`${url}${lang === locale ? '' : `/${lang}/`}`}
href={`${url}${lang === defaultLocale ? '' : `/${lang}/`}`}
hreflang={lang === 'zh-tw' ? 'zh-TW' : lang}
/>
))}

View file

@ -3,10 +3,10 @@ import themeConfig from '@/config'
import { generateMultiLangPaths } from '@/i18n/route'
import { generateRSS } from '@/utils/rss'
const { moreLocale } = themeConfig.global
const { moreLocales } = themeConfig.global
// Type for supported non-default languages
type SupportedLanguage = typeof moreLocale[number]
type SupportedLanguage = typeof moreLocales[number]
// Generate static paths for all supported languages
export function getStaticPaths() {
@ -17,7 +17,7 @@ export async function GET({ params }: APIContext) {
const lang = params.lang as SupportedLanguage
// Return 404 if language is not supported
if (!moreLocale.includes(lang)) {
if (!moreLocales.includes(lang)) {
return new Response(null, {
status: 404,
statusText: 'Not found',

View file

@ -1,5 +1,6 @@
---
import Waline from '@/components/Comments/Waline.astro'
import Comments from '@/components/Comments/index.astro'
import PostTime from '@/components/PostTime.astro'
import { generateMultiLangPostPaths } from '@/i18n/route'
import Layout from '@/layouts/Layout.astro'
import { checkSlugDuplication } from '@/utils/content'
@ -17,6 +18,7 @@ export async function getStaticPaths() {
return generateMultiLangPostPaths(posts)
}
const { lang } = Astro.params
const { post, supportedLangs = [] } = Astro.props
const description = generateDescription(post)
const { Content, remarkPluginFrontmatter } = await post.render()
@ -28,13 +30,45 @@ const { Content, remarkPluginFrontmatter } = await post.render()
postSlug={post.slug}
supportedLangs={supportedLangs}
>
<article>
<h1>{post.data.title}</h1>
<time>
{post.data.published.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit' }).replace('/', '-')}
{remarkPluginFrontmatter.minutes && <span> {remarkPluginFrontmatter.minutes} min</span>}
</time>
<article class="heti mb-12.6">
<h1 class="post-title">
<span
transition:name={`post-${post.data.abbrlink || post.slug}`}
data-disable-transition-on-theme
>
{post.data.title}
</span>
</h1>
<div
class="mb-17 block c-primary font-time"
transition:name={`time-${post.data.abbrlink || post.slug}`}
data-disable-transition-on-theme
>
<!-- published and updated time -->
<PostTime
date={post.data.published}
updatedDate={post.data.updated}
minutes={remarkPluginFrontmatter.minutes}
/>
</div>
<Content />
</article>
<Waline />
<!-- Tags -->
{post.data.tags && post.data.tags.length > 0 && (
<div class="uno-decorative-line"></div>
<div class="uno-tags-wrapper">
{post.data.tags.map(tag => (
<a
href={`/${lang}/tags/${tag}/`}
class="uno-tags-style"
>
{tag}
</a>
))}
</div>
)}
<Comments />
</Layout>

View file

@ -3,10 +3,10 @@ import themeConfig from '@/config'
import { generateMultiLangPaths } from '@/i18n/route'
import { generateRSS } from '@/utils/rss'
const { moreLocale } = themeConfig.global
const { moreLocales } = themeConfig.global
// Type for supported non-default languages
type SupportedLanguage = typeof moreLocale[number]
type SupportedLanguage = typeof moreLocales[number]
// Generate static paths for all supported languages
export function getStaticPaths() {
@ -17,7 +17,7 @@ export async function GET({ params }: APIContext) {
const lang = params.lang as SupportedLanguage
// Return 404 if language is not supported
if (!moreLocale.includes(lang)) {
if (!moreLocales.includes(lang)) {
return new Response(null, {
status: 404,
statusText: 'Not found',

View file

@ -1,4 +1,5 @@
---
import PostList from '@/components/PostList.astro'
import { generateMultiLangTagPaths } from '@/i18n/route'
import Layout from '@/layouts/Layout.astro'
import { getAllTags, getPostsByTag } from '@/utils/content'
@ -15,21 +16,23 @@ const allTags = await getAllTags()
---
<Layout>
<div>
<div class="uno-decorative-line"></div>
<div class="uno-tags-wrapper">
{allTags.map(tag => (
<a href={`/${lang}/tags/${tag}/`}>
<a
href={`/${lang}/tags/${tag}/`}
class={`uno-tags-style ${
Astro.props.tag === tag
? 'uno-tag-active'
: ''
}`}
>
{tag}
</a>
))}
</div>
<div>
<ul>
{posts.map(post => (
<li>
<a href={`/${lang}/posts/${post.data.abbrlink || post.slug}/`}>{post.data.title}</a>
</li>
))}
</ul>
<div class="mt-10.625">
<PostList posts={posts} lang={lang} />
</div>
</Layout>

View file

@ -12,9 +12,13 @@ const allTags = await getAllTags()
---
<Layout>
<div>
<div class="uno-decorative-line"></div>
<div class="uno-tags-wrapper">
{allTags.map(tag => (
<a href={`/${lang}/tags/${tag}/`}>
<a
href={`/${lang}/tags/${tag}/`}
class="uno-tags-style"
>
{tag}
</a>
))}

View file

@ -31,7 +31,7 @@ export interface ThemeConfig {
global: {
locale: typeof langPath[number]
moreLocale: typeof langPath[number][]
moreLocales: typeof langPath[number][]
fontStyle: 'sans' | 'serif'
dateFormat: 'YYYY-MM-DD' | 'MM-DD-YYYY' | 'DD-MM-YYYY' | 'MONTH DAY YYYY' | 'DAY MONTH YYYY'
titleSpace: 1 | 2 | 3