refactor: optimize multilingual article routing logic, modify slug to abbrlink

This commit is contained in:
radishzzz 2025-03-11 00:34:30 +00:00
parent 16491dae50
commit e5165dd740
16 changed files with 673 additions and 133 deletions

View file

@ -59,6 +59,7 @@
"*.mdx": "markdown"
},
"cSpell.words": [
"abbrlink",
"antfu",
"Artículos",
"astrojs",

142
pnpm-lock.yaml generated
View file

@ -95,7 +95,7 @@ importers:
devDependencies:
'@antfu/eslint-config':
specifier: ^4.8.1
version: 4.8.1(@typescript-eslint/utils@8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(@unocss/eslint-plugin@66.1.0-beta.3(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(@vue/compiler-sfc@3.5.13)(astro-eslint-parser@1.2.1)(eslint-plugin-astro@1.3.1(eslint@9.22.0(jiti@2.4.2)))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
version: 4.8.1(@typescript-eslint/utils@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(@unocss/eslint-plugin@66.1.0-beta.3(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(@vue/compiler-sfc@3.5.13)(astro-eslint-parser@1.2.1)(eslint-plugin-astro@1.3.1(eslint@9.22.0(jiti@2.4.2)))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@types/markdown-it':
specifier: ^14.1.2
version: 14.1.2
@ -937,51 +937,51 @@ packages:
'@types/web-bluetooth@0.0.21':
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
'@typescript-eslint/eslint-plugin@8.26.0':
resolution: {integrity: sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==}
'@typescript-eslint/eslint-plugin@8.26.1':
resolution: {integrity: sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
'@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/parser@8.26.0':
resolution: {integrity: sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==}
'@typescript-eslint/parser@8.26.1':
resolution: {integrity: sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/scope-manager@8.26.0':
resolution: {integrity: sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==}
'@typescript-eslint/scope-manager@8.26.1':
resolution: {integrity: sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/type-utils@8.26.0':
resolution: {integrity: sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==}
'@typescript-eslint/type-utils@8.26.1':
resolution: {integrity: sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/types@8.26.0':
resolution: {integrity: sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==}
'@typescript-eslint/types@8.26.1':
resolution: {integrity: sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.26.0':
resolution: {integrity: sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==}
'@typescript-eslint/typescript-estree@8.26.1':
resolution: {integrity: sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/utils@8.26.0':
resolution: {integrity: sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==}
'@typescript-eslint/utils@8.26.1':
resolution: {integrity: sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/visitor-keys@8.26.0':
resolution: {integrity: sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==}
'@typescript-eslint/visitor-keys@8.26.1':
resolution: {integrity: sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@ungap/structured-clone@1.3.0':
@ -1731,8 +1731,8 @@ packages:
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
eslint-plugin-jsdoc@50.6.3:
resolution: {integrity: sha512-NxbJyt1M5zffPcYZ8Nb53/8nnbIScmiLAMdoe0/FAszwb7lcSiX3iYBTsuF7RV84dZZJC8r3NghomrUXsmWvxQ==}
eslint-plugin-jsdoc@50.6.5:
resolution: {integrity: sha512-SNvAP/xdq5caeS3T5dZzBqtuYNmaXpG2F1I3+HKf2Csd0C/F6MoxQQ5NiX1nk9WhiZGhpZhfxqJkOfc36HRrAQ==}
engines: {node: '>=18'}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
@ -2887,8 +2887,8 @@ packages:
engines: {node: '>=10.13.0'}
hasBin: true
prismjs@1.29.0:
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
prismjs@1.30.0:
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
engines: {node: '>=6'}
prompts@2.4.2:
@ -3809,16 +3809,16 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.8
'@jridgewell/trace-mapping': 0.3.25
'@antfu/eslint-config@4.8.1(@typescript-eslint/utils@8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(@unocss/eslint-plugin@66.1.0-beta.3(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(@vue/compiler-sfc@3.5.13)(astro-eslint-parser@1.2.1)(eslint-plugin-astro@1.3.1(eslint@9.22.0(jiti@2.4.2)))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
'@antfu/eslint-config@4.8.1(@typescript-eslint/utils@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(@unocss/eslint-plugin@66.1.0-beta.3(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(@vue/compiler-sfc@3.5.13)(astro-eslint-parser@1.2.1)(eslint-plugin-astro@1.3.1(eslint@9.22.0(jiti@2.4.2)))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
dependencies:
'@antfu/install-pkg': 1.0.0
'@clack/prompts': 0.10.0
'@eslint-community/eslint-plugin-eslint-comments': 4.4.1(eslint@9.22.0(jiti@2.4.2))
'@eslint/markdown': 6.3.0
'@stylistic/eslint-plugin': 4.2.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/eslint-plugin': 8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/parser': 8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@vitest/eslint-plugin': 1.1.36(@typescript-eslint/utils@8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/eslint-plugin': 8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/parser': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@vitest/eslint-plugin': 1.1.36(@typescript-eslint/utils@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
ansis: 3.17.0
cac: 6.7.14
eslint: 9.22.0(jiti@2.4.2)
@ -3828,7 +3828,7 @@ snapshots:
eslint-plugin-antfu: 3.1.1(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-command: 3.1.0(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-import-x: 4.6.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
eslint-plugin-jsdoc: 50.6.3(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-jsdoc: 50.6.5(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-jsonc: 2.19.1(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-n: 17.16.2(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-no-only-tests: 3.3.0
@ -3837,7 +3837,7 @@ snapshots:
eslint-plugin-regexp: 2.7.0(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-toml: 0.12.0(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-unicorn: 57.0.0(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-vue: 10.0.0(eslint@9.22.0(jiti@2.4.2))(vue-eslint-parser@10.1.1(eslint@9.22.0(jiti@2.4.2)))
eslint-plugin-yml: 1.17.0(eslint@9.22.0(jiti@2.4.2))
eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.13)(eslint@9.22.0(jiti@2.4.2))
@ -3957,7 +3957,7 @@ snapshots:
'@astrojs/prism@3.2.0':
dependencies:
prismjs: 1.29.0
prismjs: 1.30.0
'@astrojs/rss@4.0.11':
dependencies:
@ -4054,7 +4054,7 @@ snapshots:
dependencies:
'@types/eslint': 9.6.1
'@types/estree': 1.0.6
'@typescript-eslint/types': 8.26.0
'@typescript-eslint/types': 8.26.1
comment-parser: 1.4.1
esquery: 1.6.0
jsdoc-type-pratt-parser: 4.1.0
@ -4489,7 +4489,7 @@ snapshots:
'@stylistic/eslint-plugin@4.2.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
dependencies:
'@typescript-eslint/utils': 8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
eslint: 9.22.0(jiti@2.4.2)
eslint-visitor-keys: 4.2.0
espree: 10.3.0
@ -4587,14 +4587,14 @@ snapshots:
'@types/web-bluetooth@0.0.21': {}
'@typescript-eslint/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
'@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
dependencies:
'@eslint-community/regexpp': 4.12.1
'@typescript-eslint/parser': 8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/scope-manager': 8.26.0
'@typescript-eslint/type-utils': 8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/utils': 8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/visitor-keys': 8.26.0
'@typescript-eslint/parser': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/scope-manager': 8.26.1
'@typescript-eslint/type-utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/visitor-keys': 8.26.1
eslint: 9.22.0(jiti@2.4.2)
graphemer: 1.4.0
ignore: 5.3.2
@ -4604,27 +4604,27 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
'@typescript-eslint/parser@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
dependencies:
'@typescript-eslint/scope-manager': 8.26.0
'@typescript-eslint/types': 8.26.0
'@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2)
'@typescript-eslint/visitor-keys': 8.26.0
'@typescript-eslint/scope-manager': 8.26.1
'@typescript-eslint/types': 8.26.1
'@typescript-eslint/typescript-estree': 8.26.1(typescript@5.8.2)
'@typescript-eslint/visitor-keys': 8.26.1
debug: 4.4.0
eslint: 9.22.0(jiti@2.4.2)
typescript: 5.8.2
transitivePeerDependencies:
- supports-color
'@typescript-eslint/scope-manager@8.26.0':
'@typescript-eslint/scope-manager@8.26.1':
dependencies:
'@typescript-eslint/types': 8.26.0
'@typescript-eslint/visitor-keys': 8.26.0
'@typescript-eslint/types': 8.26.1
'@typescript-eslint/visitor-keys': 8.26.1
'@typescript-eslint/type-utils@8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
'@typescript-eslint/type-utils@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
dependencies:
'@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2)
'@typescript-eslint/utils': 8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/typescript-estree': 8.26.1(typescript@5.8.2)
'@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
debug: 4.4.0
eslint: 9.22.0(jiti@2.4.2)
ts-api-utils: 2.0.1(typescript@5.8.2)
@ -4632,12 +4632,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@8.26.0': {}
'@typescript-eslint/types@8.26.1': {}
'@typescript-eslint/typescript-estree@8.26.0(typescript@5.8.2)':
'@typescript-eslint/typescript-estree@8.26.1(typescript@5.8.2)':
dependencies:
'@typescript-eslint/types': 8.26.0
'@typescript-eslint/visitor-keys': 8.26.0
'@typescript-eslint/types': 8.26.1
'@typescript-eslint/visitor-keys': 8.26.1
debug: 4.4.0
fast-glob: 3.3.3
is-glob: 4.0.3
@ -4648,20 +4648,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
'@typescript-eslint/utils@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.22.0(jiti@2.4.2))
'@typescript-eslint/scope-manager': 8.26.0
'@typescript-eslint/types': 8.26.0
'@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2)
'@typescript-eslint/scope-manager': 8.26.1
'@typescript-eslint/types': 8.26.1
'@typescript-eslint/typescript-estree': 8.26.1(typescript@5.8.2)
eslint: 9.22.0(jiti@2.4.2)
typescript: 5.8.2
transitivePeerDependencies:
- supports-color
'@typescript-eslint/visitor-keys@8.26.0':
'@typescript-eslint/visitor-keys@8.26.1':
dependencies:
'@typescript-eslint/types': 8.26.0
'@typescript-eslint/types': 8.26.1
eslint-visitor-keys: 4.2.0
'@ungap/structured-clone@1.3.0': {}
@ -4703,7 +4703,7 @@ snapshots:
'@unocss/eslint-plugin@66.1.0-beta.3(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
dependencies:
'@typescript-eslint/utils': 8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@unocss/config': 66.1.0-beta.3
'@unocss/core': 66.1.0-beta.3
'@unocss/rule-utils': 66.1.0-beta.3
@ -4837,9 +4837,9 @@ snapshots:
transitivePeerDependencies:
- vue
'@vitest/eslint-plugin@1.1.36(@typescript-eslint/utils@8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
'@vitest/eslint-plugin@1.1.36(@typescript-eslint/utils@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)':
dependencies:
'@typescript-eslint/utils': 8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
eslint: 9.22.0(jiti@2.4.2)
optionalDependencies:
typescript: 5.8.2
@ -5091,8 +5091,8 @@ snapshots:
astro-eslint-parser@1.2.1:
dependencies:
'@astrojs/compiler': 2.10.4
'@typescript-eslint/scope-manager': 8.26.0
'@typescript-eslint/types': 8.26.0
'@typescript-eslint/scope-manager': 8.26.1
'@typescript-eslint/types': 8.26.1
astrojs-compiler-sync: 1.0.1(@astrojs/compiler@2.10.4)
debug: 4.4.0
entities: 6.0.0
@ -5629,7 +5629,7 @@ snapshots:
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.22.0(jiti@2.4.2))
'@jridgewell/sourcemap-codec': 1.5.0
'@typescript-eslint/types': 8.26.0
'@typescript-eslint/types': 8.26.1
astro-eslint-parser: 1.2.1
eslint: 9.22.0(jiti@2.4.2)
eslint-compat-utils: 0.6.4(eslint@9.22.0(jiti@2.4.2))
@ -5654,8 +5654,8 @@ snapshots:
eslint-plugin-import-x@4.6.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2):
dependencies:
'@types/doctrine': 0.0.9
'@typescript-eslint/scope-manager': 8.26.0
'@typescript-eslint/utils': 8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/scope-manager': 8.26.1
'@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
debug: 4.4.0
doctrine: 3.0.0
enhanced-resolve: 5.18.1
@ -5671,7 +5671,7 @@ snapshots:
- supports-color
- typescript
eslint-plugin-jsdoc@50.6.3(eslint@9.22.0(jiti@2.4.2)):
eslint-plugin-jsdoc@50.6.5(eslint@9.22.0(jiti@2.4.2)):
dependencies:
'@es-joy/jsdoccomment': 0.49.0
are-docs-informative: 0.0.2
@ -5718,8 +5718,8 @@ snapshots:
eslint-plugin-perfectionist@4.10.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2):
dependencies:
'@typescript-eslint/types': 8.26.0
'@typescript-eslint/utils': 8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/types': 8.26.1
'@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
eslint: 9.22.0(jiti@2.4.2)
natural-orderby: 5.0.0
transitivePeerDependencies:
@ -5774,11 +5774,11 @@ snapshots:
semver: 7.7.1
strip-indent: 4.0.0
eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2)):
eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2)):
dependencies:
eslint: 9.22.0(jiti@2.4.2)
optionalDependencies:
'@typescript-eslint/eslint-plugin': 8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/eslint-plugin': 8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
eslint-plugin-vue@10.0.0(eslint@9.22.0(jiti@2.4.2))(vue-eslint-parser@10.1.1(eslint@9.22.0(jiti@2.4.2))):
dependencies:
@ -7275,7 +7275,7 @@ snapshots:
prettier@2.8.7:
optional: true
prismjs@1.29.0: {}
prismjs@1.30.0: {}
prompts@2.4.2:
dependencies:

View file

@ -2,22 +2,22 @@
// Define props received by the component
interface Post {
data: {
title: string;
slug?: string;
published: Date;
};
slug?: string;
title: string
abbrlink?: string
published: Date
}
slug?: string
remarkPluginFrontmatter?: {
minutes?: number;
};
minutes?: number
}
}
// Receive posts parameter passed from outside
const { posts } = Astro.props;
const { posts } = Astro.props
// Declare the type of props
export interface Props {
posts: Post[];
posts: Post[]
}
---
<ul>
@ -27,8 +27,8 @@ export interface Props {
<a
class="hover:c-primary"
href={`/posts/${post.data.slug || post.slug}`}
transition:name={`post-${post.data.slug || post.slug}`}
href={`/posts/${post.data.abbrlink || post.slug}/`}
transition:name={`post-${post.data.abbrlink || post.slug}`}
data-disable-transition-on-theme
>
{post.data.title}
@ -36,7 +36,7 @@ export interface Props {
<div
class="uno-mobile-time"
transition:name={`time-${post.data.slug || post.slug}`}
transition:name={`time-${post.data.abbrlink || post.slug}`}
data-disable-transition-on-theme
>
<time>

View file

@ -1,14 +1,64 @@
---
import themeConfig from '@/config'
import { getPagePath } from '@/utils/i18n/path'
import { getCollection } from 'astro:content'
// Language array with empty string as default locale
const langs = ['', ...themeConfig.global.moreLocale]
const currentLocale = themeConfig.global.locale
// 获取当前页面路径信息
const { isPost } = getPagePath(Astro.url.pathname)
function getLanguageDisplayName(code: string) {
if (!code) {
return 'Default'
// 获取默认语言
const defaultLocale = themeConfig.global.locale
// 定义固定的语言顺序(按照要求的顺序)
const fixedLangOrder = [defaultLocale, ...themeConfig.global.moreLocale]
// 获取当前文章的可用语言
let availableLangs: string[] = []
let currentPostSlug = ''
// 如果是文章页,获取当前文章对象
if (isPost) {
// 从URL中提取文章slug
const pathParts = Astro.url.pathname.split('/')
const slugIndex = pathParts.findIndex(part => part === 'posts') + 1
if (slugIndex > 0 && pathParts.length > slugIndex) {
currentPostSlug = pathParts[slugIndex]
// 获取所有文章
const posts = await getCollection('posts')
// 找到所有具有相同abbrlink或slug的文章
const relatedPosts = posts.filter(post =>
post.data.abbrlink === currentPostSlug || post.slug === currentPostSlug,
)
if (relatedPosts.length > 0) {
// 收集所有相关文章支持的语言
const supportedLangs = new Set()
relatedPosts.forEach((post) => {
// 处理lang属性
if (typeof post.data.lang === 'string' && post.data.lang.trim() !== '') {
supportedLangs.add(post.data.lang)
}
// 如果没有指定语言,则假定支持默认语言
else {
supportedLangs.add(defaultLocale)
}
})
// 按照固定顺序筛选出可用的语言
availableLangs = fixedLangOrder.filter(lang => supportedLangs.has(lang))
}
}
}
else {
// 非文章页面使用所有语言
availableLangs = fixedLangOrder
}
// 当前语言
function getLanguageDisplayName(code: string) {
return new Intl.DisplayNames(['en'], { type: 'language' }).of(code) || code
}
---
@ -17,7 +67,7 @@ function getLanguageDisplayName(code: string) {
type="button"
id="language-switcher"
class="aspect-square w-4 c-secondary active:scale-90"
aria-label={`Current Language: ${getLanguageDisplayName(currentLocale)}. Click to switch to next language.`}
aria-label={`Current Language: ${getLanguageDisplayName(defaultLocale)}. Click to switch to next language.`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -29,7 +79,7 @@ function getLanguageDisplayName(code: string) {
</svg>
</button>
<script is:inline define:vars={{ langs }}>
<script is:inline define:vars={{ availableLangs, currentPostSlug, isPost, defaultLocale }}>
document.addEventListener('astro:page-load', () => {
const langSwitch = document.getElementById('language-switcher')
@ -38,30 +88,53 @@ document.addEventListener('astro:page-load', () => {
const segments = pathname.split('/').filter(Boolean)
const firstSegment = segments[0] || ''
// Get current language or empty string if invalid
const currentLang = langs.includes(firstSegment)
? firstSegment
: ''
// 获取当前语言
let currentLang = defaultLocale
if (availableLangs.includes(firstSegment) && firstSegment !== defaultLocale) {
currentLang = firstSegment
}
const currentIndex = langs.indexOf(currentLang)
const nextLang = langs[(currentIndex + 1) % langs.length]
// 获取下一个语言
const currentIndex = availableLangs.indexOf(currentLang)
const nextLang = availableLangs[(currentIndex + 1) % availableLangs.length]
const newPath = buildNewPath(currentLang, nextLang, segments, pathname) || '/'
const newPath = buildNewPath(currentLang, nextLang, segments) || '/'
window.location.href = `${newPath}${search}${hash}`
})
})
function buildNewPath(currentLang, nextLang, segments, pathname) {
if (currentLang) {
segments[0] = nextLang || segments[0]
function buildNewPath(currentLang, nextLang, segments) {
// 创建一个新的segments数组副本避免修改原始数组
const newSegments = [...segments]
const firstSegment = newSegments[0] || ''
return nextLang
? `/${segments.join('/')}`
: `/${segments.slice(1).join('/')}`
// 检查第一段是否是语言代码(不包括默认语言)
const isFirstSegmentLang = availableLangs.includes(firstSegment) && firstSegment !== defaultLocale
// 处理不同的情况
if (nextLang === defaultLocale) {
// 如果下一个语言是默认语言,则移除语言段
if (isFirstSegmentLang) {
newSegments.shift()
}
}
else {
// 如果下一个语言不是默认语言
if (isFirstSegmentLang) {
// 如果第一段是语言代码,则替换它
newSegments[0] = nextLang
}
else {
// 如果第一段不是语言代码,则添加新的语言代码
newSegments.unshift(nextLang)
}
}
return nextLang
? `/${nextLang}${pathname}`
: pathname
// 返回新路径
// 处理空路径的特殊情况,避免生成 // 这样的路径
if (newSegments.length === 0) {
return '/'
}
return `/${newSegments.join('/')}/`
}
</script>

View file

@ -14,9 +14,9 @@ const postsCollection = defineCollection({
toc: z.boolean().optional().default(false),
draft: z.boolean().optional().default(false),
lang: z.string().optional().default(''),
slug: z.string().optional().default('').refine(
slug => !slug || /^[\w\-]*$/.test(slug),
{ message: 'Slug can only contain letters, numbers, hyphens and underscores' },
abbrlink: z.string().optional().default('').refine(
abbrlink => !abbrlink || /^[a-z0-9\-]*$/.test(abbrlink),
{ message: 'Abbrlink can only contain letters, numbers, hyphens and underscores' },
),
minutes: z.number().optional(),
}),

View file

@ -0,0 +1,190 @@
---
title: 故鄉
published: 1921-01-10
tags: ["鲁迅","经典读本"]
lang: ja
abbrlink: hometown
---
import { Image } from 'astro:assets';
lang: ja
<Image src="https://image.radishzz.cc/picsmaller/03.webp" inferSize alt="这是图片的说明文字" />
我冒了嚴寒,回到相隔二千餘里,別了二十餘年的故鄉去。
時候既然是深冬;漸近故鄉時,天氣又陰晦了,冷風吹進船艙中,嗚嗚的響,從蓬隙向外一望,蒼黃的天底下,遠近橫著幾個蕭索的荒村,沒有一些活氣。我的心禁不住悲涼起來了。
阿!這不是我二十年來時時記得的故鄉?
我所記得的故鄉全不如此。我的故鄉好得多了。但要我記起他的美麗,說出他的佳處來,卻又沒有影像,沒有言辭了。仿佛也就如此。於是我自己解釋說:故鄉本也如此,——雖然沒有進步,也未必有如我所感的悲涼,這只是我自己心情的改變罷了,因為我這次回鄉,本沒有什麼好心緒。
我這次是專為了別他而來的。我們多年聚族而居的老屋,已經公同賣給別姓了,交屋的期限,只在本年,所以必須趕在正月初一以前,永別了熟識的老屋,而且遠離了熟識的故鄉,搬家到我在謀食的異地去。
第二日清早晨我到了我家的門口了。瓦楞上許多枯草的斷莖當風抖著,正在說明這老屋難免易主的原因。幾房的本家大約已經搬走了,所以很寂靜。我到了自家的房外,我的母親早已迎著出來了,接著便飛出了八歲的侄兒宏兒。 -->
我的母親很高興,但也藏著許多淒涼的神情,教我坐下,歇息,喝茶,且不談搬家的事。宏兒沒有見過我,遠遠的對面站著只是看。
但我們終於談到搬家的事。我說外間的寓所已經租定了,又買了幾件傢具,此外須將家裡所有的木器賣去,再去增添。母親也說好,而且行李也略已齊集,木器不便搬運的,也小半賣去了,只是收不起錢來。
「你休息一兩天,去拜望親戚本家一回,我們便可以走了。」母親說。
「是的。」
「還有閏土,他每到我家來時,總問起你,很想見你一回面。我已經將你到家的大約日期通知他,他也許就要來了。」
這時候,我的腦裡忽然閃出一幅神異的圖畫來:深藍的天空中掛著一輪金黃的圓月,下面是海邊的沙地,都種著一望無際的碧綠的西瓜,其間有一個十一二歲的少年,項帶銀圈,手捏一柄鋼叉,向一匹猹盡力的刺去,那猹卻將身一扭,反從他的胯下逃走了。
這少年便是閏土。我認識他時,也不過十多歲,離現在將有三十年了;那時我的父親還在世,家景也好,我正是一個少爺。那一年,我家是一件大祭祀的值年。這祭祀,說是三十多年才能輪到一回,所以很鄭重;正月裡供祖像,供品很多,祭器很講究,拜的人也很多,祭器也很要防偷去。我家只有一個忙月(我們這裡給人做工的分三種:整年給一定人家做工的叫長工;按日給人做工的叫短工;自己也種地,只在過年過節以及收租時候來給一定人家做工的稱忙月),忙不過來,他便對父親說,可以叫他的兒子閏土來管祭器的。
我的父親允許了;我也很高興,因為我早聽到閏土這名字,而且知道他和我仿佛年紀,閏月生的,五行缺土,所以他的父親叫他閏土。他是能裝弶捉小鳥雀的。
我於是日日盼望新年,新年到,閏土也就到了。好容易到了年末,有一日,母親告訴我,閏土來了,我便飛跑的去看。他正在廚房裡,紫色的圓臉,頭戴一頂小氈帽,頸上套一個明晃晃的銀項圈,這可見他的父親十分愛他,怕他死去,所以在神佛面前許下願心,用圈子將他套住了。他見人很怕羞,只是不怕我,沒有旁人的時候,便和我說話,於是不到半日,我們便熟識了。
我們那時候不知道談些什麼,只記得閏土很高興,說是上城之後,見了許多沒有見過的東西。
第二日,我便要他捕鳥。他說:
“這不能。須大雪下了才好。我們沙地上,下了雪,我掃出一塊空地來,用短棒支起一個大竹匾,撒下秕穀,看鳥雀來吃時,我遠遠地將縛在棒上的繩子只一拉,那鳥雀就罩在竹匾下了。什麼都有:稻雞,角雞,鵓鴣,藍背……”
我於是又很盼望下雪。
閏土又對我說:
“現在太冷,你夏天到我們這裡來。我們日裡到海邊撿貝殼去,紅的綠的都有,鬼見怕也有,觀音手也有。晚上我和爹管西瓜去,你也去。”
“管賊麽?”
“不是。走路的人口渴了摘一個瓜吃,我們這裡是不算偷的。要管的是獾豬,刺蝟,猹。月亮底下,你聽,啦啦的響了,猹在咬瓜了。你便捏了胡叉,輕輕地走去……”
我那時並不知道這所謂猹的是怎麼一件東西——便是現在也沒有知道——只是無端的覺得狀如小狗而很兇猛。
“他不咬人麽?”
“有胡叉呢。走到了,看見猹了,你便刺。這畜生很伶俐,倒向你奔來,反從胯下竄了。他的皮毛是油一般的滑……”
我素不知道天下有這許多新鮮事:海邊有如許五色的貝殼;西瓜有這樣危險的經歷,我先前單知道他在水果店裡出賣罷了。
“我們沙地裡,潮汛要來的時候,就有許多跳魚兒只是跳,都有青蛙似的兩個腳……”
阿!閏土的心裡有無窮無盡的希奇的事,都是我往常的朋友所不知道的。他們不知道一些事,閏土在海邊時,他們都和我一樣只看見院子裡高牆上的四角的天空。
可惜正月過去了,閏土須回家裡去,我急得大哭,他也躲到廚房裡,哭著不肯出門,但終於被他父親帶走了。他後來還托他的父親帶給我一包貝殼和幾支很好看的鳥毛,我也曾送他一兩次東西,但從此沒有再見面。
現在我的母親提起了他,我這兒時的記憶,忽而全都閃電似的蘇生過來,似乎看到了我的美麗的故鄉了。我應聲說:
“這好極!他,——怎樣?……”
“他?……他景況也很不如意……”母親說著,便向房外看,“這些人又來了。說是買木器,順手也就隨便拿走的,我得去看看。”
母親站起身,出去了。門外有幾個女人的聲音。我便招宏兒走近面前,和他閑話:問他可會寫字,可願意出門。
“我們坐火車去麽?”
“我們坐火車去。”
“船呢?”
“先坐船,……”
“哈!這模樣了!鬍子這麼長了!”一種尖利的怪聲突然大叫起來。
我吃了一嚇,趕忙抬起頭,卻見一個凸顴骨、薄嘴唇、五十歲上下的女人站在我面前,兩手搭在髀間,沒有繫裙,張著兩腳,正像一個畫圖儀器裡細腳伶仃的圓規。
我愕然了。
“不認識了麽?我還抱過你咧!”
我愈加愕然了。幸而我的母親也就進來,從旁說:
“他多年出門,統忘卻了。你該記得罷,”便向著我說,“這是斜對門的楊二嫂,……開豆腐店的。”
哦,我記得了。我孩子時候,在斜對門的豆腐店裡確乎終日坐著一個楊二嫂,人都叫伊“豆腐西施”。但是擦著白粉,顴骨沒有這麼高,嘴唇也沒有這麼薄,而且終日坐著,我也從沒有見過這圓規式的姿勢。那時人說:因為伊,這豆腐店的買賣非常好。但這大約因為年齡的關係,我卻並未蒙著一毫感化,所以竟完全忘卻了。然而圓規很不平,顯出鄙夷的神色,仿佛嗤笑法國人不知道拿破侖,美國人不知道華盛頓似的,冷笑說:
“忘了?這真是貴人眼高……”
“那有這事……我……”我惶恐著,站起來說。
“那麼,我對你說。迅哥兒,你闊了,搬動又笨重,你還要什麼這些破爛木器,讓我拿去罷。我們小戶人家,用得著。”
“我並沒有闊哩。我須賣了這些,再去……”
“阿呀呀,你放了道台了,還說不闊?你現在有三房姨太太;出門便是八抬的大轎,還說不闊?嚇,什麼都瞞不過我。”
我知道無話可說了,便閉了口,默默的站著。
“阿呀阿呀,真是愈有錢,便愈是一毫不肯放鬆,愈是一毫不肯放鬆,便愈有錢……”圓規一面憤憤的迴轉身,一面絮絮的說,慢慢向外走,順便將我母親的一副手套塞在褲腰裡,出去了。
此後又有近處的本家和親戚來訪問我。我一面應酬,偷空便收拾些行李,這樣的過了三四天。
一日是天氣很冷的午後,我吃過午飯,坐著喝茶,覺得外面有人進來了,便回頭去看。我看時,不由的非常出驚,慌忙站起身,迎著走去。
這來的便是閏土。雖然我一見便知道是閏土,但又不是我這記憶上的閏土了。他身材增加了一倍;先前的紫色的圓臉,已經變作灰黃,而且加上了很深的皺紋;眼睛也像他父親一樣,周圍都腫得通紅,這我知道,在海邊種地的人,終日吹著海風,大抵是這樣的。他頭上是一頂破氈帽,身上只一件極薄的棉衣,渾身瑟索著;手裡提著一個紙包和一支長煙管,那手也不是我所記得的紅活圓實的手,卻又粗又笨而且開裂,像是松樹皮了。
我這時很興奮,但不知道怎麼說才好,只是說:
“阿!閏土哥,——你來了?……”
我接著便有許多話,想要連珠一般湧出:角雞,跳魚兒,貝殼,猹,……但又總覺得被什麼擋著似的,單在腦裡面迴旋,吐不出口外去。
他站住了,臉上現出歡喜和淒涼的神情;動著嘴唇,卻沒有作聲。他的態度終於恭敬起來了,分明的叫道:
“老爺!……”
我似乎打了一個寒噤;我就知道,我們之間已經隔了一層可悲的厚障壁了。我也說不出話。
他回過頭去說,“水生,給老爺磕頭。”便拖出躲在背後的孩子來,這正是一個廿年前的閏土,只是黃瘦些,頸子上沒有銀圈罷了。“這是第五個孩子,沒有見過世面,躲躲閃閃……”
母親和宏兒下樓來了,他們大約也聽到了聲音。
“老太太。信是早收到了。我實在喜歡的不得了,知道老爺回來……”閏土說。
“阿,你怎的這樣客氣起來。你們先前不是哥弟稱呼麽?還是照舊:迅哥兒。”母親高興的說。
“阿呀,老太太真是……這成什麼規矩。那時是孩子,不懂事……”閏土說著,又叫水生上來打拱,那孩子卻害羞,緊緊的只貼在他背後。
“他就是水生?第五個?都是生人,怕生也難怪的;還是宏兒和他去走走。”母親說。
宏兒聽得這話,便來招水生,水生卻鬆鬆爽爽同他一路出去了。母親叫閏土坐,他遲疑了一回,終於就了坐,將長煙管靠在桌旁,遞過紙包來,說:
“冬天沒有什麼東西了。這一點乾青豆倒是自家曬在那裡的,請老爺……”
我問問他的景況。他只是搖頭。
“非常難。第六個孩子也會幫忙了,卻總是吃不夠……又不太平……什麼地方都要錢,沒有規定……收成又壞。種出東西來,挑去賣,總要捐幾回錢,折了本;不去賣,又只能爛掉……”
他只是搖頭;臉上雖然刻著許多皺紋,卻全然不動,仿佛石像一般。他大約只是覺得苦,卻又形容不出,沉默了片時,便拿起煙管來默默的吸煙了。
母親問他,知道他的家裡事務忙,明天便得回去;又沒有吃過午飯,便叫他自己到廚下炒飯吃去。
他出去了;母親和我都嘆息他的景況:多子,饑荒,苛稅,兵,匪,官,紳,都苦得他像一個木偶人了。母親對我說,凡是不必搬走的東西,盡可以送他,可以聽他自己去揀擇。
下午,他揀好了幾件東西:兩條長桌,四個椅子,一副香爐和燭臺,一桿抬秤。他又要所有的草灰(我們這裡煮飯是燒稻草的,那灰,可以做沙地的肥料),待我們啟程的時候,他用船來載去。
夜間,我們又談些閑天,都是無關緊要的話;第二天早晨,他就領了水生回去了。
又過了九日,是我們啟程的日期。閏土早晨便到了,水生沒有同來,卻只帶著一個五歲的女兒管船隻。我們終日很忙碌,再沒有談天的工夫。來客也不少,有送行的,有拿東西的,有送行兼拿東西的。待到傍晚我們上船的時候,這老屋裡的所有破舊大小粗細東西,已經一掃而空了。
我們的船向前走,兩岸的青山在黃昏中,都裝成了深黛顏色,連著退向船後梢去。
宏兒和我靠著船窗,同看外面模糊的風景,他忽然問道:
“大伯!我們什麼時候回來?”
“回來?你怎麼還沒有走就想回來了。”
“可是,水生約我到他家玩去咧……”他睜著大的黑眼睛,癡癡的想。
我和母親也都有些惘然,於是又提起閏土來。母親說,那豆腐西施的楊二嫂,自從我家收拾行李以來,本是每日必到的,前天伊在灰堆裡,掏出十多個碗碟來,議論之後,便定說是閏土埋著的,他可以在運灰的時候,一齊搬回家裡去;楊二嫂發見了這件事,自己很以為功,便拿了那狗氣殺(這是我們這裡養雞的器具,木盤上面有著柵欄,內盛食料,雞可以伸進頸子去啄,狗卻不能,只能看著氣死),飛也似的跑了,虧伊裝著這麼高低的小腳,竟跑得這樣快。
老屋離我愈遠了;故鄉的山水也都漸漸遠離了我,但我卻並不感到怎樣的留戀。我只覺得我四面有看不見的高牆,將我隔成孤身,使我非常氣悶;那西瓜地上的銀項圈的小英雄的影像,我本來十分清楚,現在卻忽地模糊了,又使我非常的悲哀。
母親和宏兒都睡著了。
我躺著,聽船底潺潺的水聲,知道我在走我的路。我想:我竟與閏土隔絕到這地步了,但我們的後輩還是一氣,宏兒不是正在想念水生麽。我希望他們不再像我,又大家隔膜起來……然而我又不願意他們因為要一氣,都如我的辛苦展轉而生活,也不願意他們都如閏土的辛苦麻木而生活,也不願意都如別人的辛苦恣睢而生活。他們應該有新的生活,為我們所未經生活過的。
我想到希望,忽然害怕起來了。閏土要香爐和燭臺的時候,我還暗地裡笑他,以為他總是崇拜偶像,什麼時候都不忘卻。現在我所謂希望,不也是我自己手製的偶像麽?只是他的願望切近,我的願望茫遠罷了。
我在朦朧中,眼前展開一片海邊碧綠的沙地來,上面深藍的天空中掛著一輪金黃的圓月。我想:希望本是無所謂有,無所謂無的。這正如地上的路;其實地上本沒有路,走的人多了,也便成了路。
一九二一年一月

View file

@ -0,0 +1,190 @@
---
title: 故鄉
published: 1921-01-10
tags: ["鲁迅","经典读本"]
lang: en
abbrlink: hometown
---
import { Image } from 'astro:assets';
lang: en
<Image src="https://image.radishzz.cc/picsmaller/03.webp" inferSize alt="这是图片的说明文字" />
我冒了嚴寒,回到相隔二千餘里,別了二十餘年的故鄉去。
時候既然是深冬;漸近故鄉時,天氣又陰晦了,冷風吹進船艙中,嗚嗚的響,從蓬隙向外一望,蒼黃的天底下,遠近橫著幾個蕭索的荒村,沒有一些活氣。我的心禁不住悲涼起來了。
阿!這不是我二十年來時時記得的故鄉?
我所記得的故鄉全不如此。我的故鄉好得多了。但要我記起他的美麗,說出他的佳處來,卻又沒有影像,沒有言辭了。仿佛也就如此。於是我自己解釋說:故鄉本也如此,——雖然沒有進步,也未必有如我所感的悲涼,這只是我自己心情的改變罷了,因為我這次回鄉,本沒有什麼好心緒。
我這次是專為了別他而來的。我們多年聚族而居的老屋,已經公同賣給別姓了,交屋的期限,只在本年,所以必須趕在正月初一以前,永別了熟識的老屋,而且遠離了熟識的故鄉,搬家到我在謀食的異地去。
第二日清早晨我到了我家的門口了。瓦楞上許多枯草的斷莖當風抖著,正在說明這老屋難免易主的原因。幾房的本家大約已經搬走了,所以很寂靜。我到了自家的房外,我的母親早已迎著出來了,接著便飛出了八歲的侄兒宏兒。 -->
我的母親很高興,但也藏著許多淒涼的神情,教我坐下,歇息,喝茶,且不談搬家的事。宏兒沒有見過我,遠遠的對面站著只是看。
但我們終於談到搬家的事。我說外間的寓所已經租定了,又買了幾件傢具,此外須將家裡所有的木器賣去,再去增添。母親也說好,而且行李也略已齊集,木器不便搬運的,也小半賣去了,只是收不起錢來。
「你休息一兩天,去拜望親戚本家一回,我們便可以走了。」母親說。
「是的。」
「還有閏土,他每到我家來時,總問起你,很想見你一回面。我已經將你到家的大約日期通知他,他也許就要來了。」
這時候,我的腦裡忽然閃出一幅神異的圖畫來:深藍的天空中掛著一輪金黃的圓月,下面是海邊的沙地,都種著一望無際的碧綠的西瓜,其間有一個十一二歲的少年,項帶銀圈,手捏一柄鋼叉,向一匹猹盡力的刺去,那猹卻將身一扭,反從他的胯下逃走了。
這少年便是閏土。我認識他時,也不過十多歲,離現在將有三十年了;那時我的父親還在世,家景也好,我正是一個少爺。那一年,我家是一件大祭祀的值年。這祭祀,說是三十多年才能輪到一回,所以很鄭重;正月裡供祖像,供品很多,祭器很講究,拜的人也很多,祭器也很要防偷去。我家只有一個忙月(我們這裡給人做工的分三種:整年給一定人家做工的叫長工;按日給人做工的叫短工;自己也種地,只在過年過節以及收租時候來給一定人家做工的稱忙月),忙不過來,他便對父親說,可以叫他的兒子閏土來管祭器的。
我的父親允許了;我也很高興,因為我早聽到閏土這名字,而且知道他和我仿佛年紀,閏月生的,五行缺土,所以他的父親叫他閏土。他是能裝弶捉小鳥雀的。
我於是日日盼望新年,新年到,閏土也就到了。好容易到了年末,有一日,母親告訴我,閏土來了,我便飛跑的去看。他正在廚房裡,紫色的圓臉,頭戴一頂小氈帽,頸上套一個明晃晃的銀項圈,這可見他的父親十分愛他,怕他死去,所以在神佛面前許下願心,用圈子將他套住了。他見人很怕羞,只是不怕我,沒有旁人的時候,便和我說話,於是不到半日,我們便熟識了。
我們那時候不知道談些什麼,只記得閏土很高興,說是上城之後,見了許多沒有見過的東西。
第二日,我便要他捕鳥。他說:
“這不能。須大雪下了才好。我們沙地上,下了雪,我掃出一塊空地來,用短棒支起一個大竹匾,撒下秕穀,看鳥雀來吃時,我遠遠地將縛在棒上的繩子只一拉,那鳥雀就罩在竹匾下了。什麼都有:稻雞,角雞,鵓鴣,藍背……”
我於是又很盼望下雪。
閏土又對我說:
“現在太冷,你夏天到我們這裡來。我們日裡到海邊撿貝殼去,紅的綠的都有,鬼見怕也有,觀音手也有。晚上我和爹管西瓜去,你也去。”
“管賊麽?”
“不是。走路的人口渴了摘一個瓜吃,我們這裡是不算偷的。要管的是獾豬,刺蝟,猹。月亮底下,你聽,啦啦的響了,猹在咬瓜了。你便捏了胡叉,輕輕地走去……”
我那時並不知道這所謂猹的是怎麼一件東西——便是現在也沒有知道——只是無端的覺得狀如小狗而很兇猛。
“他不咬人麽?”
“有胡叉呢。走到了,看見猹了,你便刺。這畜生很伶俐,倒向你奔來,反從胯下竄了。他的皮毛是油一般的滑……”
我素不知道天下有這許多新鮮事:海邊有如許五色的貝殼;西瓜有這樣危險的經歷,我先前單知道他在水果店裡出賣罷了。
“我們沙地裡,潮汛要來的時候,就有許多跳魚兒只是跳,都有青蛙似的兩個腳……”
阿!閏土的心裡有無窮無盡的希奇的事,都是我往常的朋友所不知道的。他們不知道一些事,閏土在海邊時,他們都和我一樣只看見院子裡高牆上的四角的天空。
可惜正月過去了,閏土須回家裡去,我急得大哭,他也躲到廚房裡,哭著不肯出門,但終於被他父親帶走了。他後來還托他的父親帶給我一包貝殼和幾支很好看的鳥毛,我也曾送他一兩次東西,但從此沒有再見面。
現在我的母親提起了他,我這兒時的記憶,忽而全都閃電似的蘇生過來,似乎看到了我的美麗的故鄉了。我應聲說:
“這好極!他,——怎樣?……”
“他?……他景況也很不如意……”母親說著,便向房外看,“這些人又來了。說是買木器,順手也就隨便拿走的,我得去看看。”
母親站起身,出去了。門外有幾個女人的聲音。我便招宏兒走近面前,和他閑話:問他可會寫字,可願意出門。
“我們坐火車去麽?”
“我們坐火車去。”
“船呢?”
“先坐船,……”
“哈!這模樣了!鬍子這麼長了!”一種尖利的怪聲突然大叫起來。
我吃了一嚇,趕忙抬起頭,卻見一個凸顴骨、薄嘴唇、五十歲上下的女人站在我面前,兩手搭在髀間,沒有繫裙,張著兩腳,正像一個畫圖儀器裡細腳伶仃的圓規。
我愕然了。
“不認識了麽?我還抱過你咧!”
我愈加愕然了。幸而我的母親也就進來,從旁說:
“他多年出門,統忘卻了。你該記得罷,”便向著我說,“這是斜對門的楊二嫂,……開豆腐店的。”
哦,我記得了。我孩子時候,在斜對門的豆腐店裡確乎終日坐著一個楊二嫂,人都叫伊“豆腐西施”。但是擦著白粉,顴骨沒有這麼高,嘴唇也沒有這麼薄,而且終日坐著,我也從沒有見過這圓規式的姿勢。那時人說:因為伊,這豆腐店的買賣非常好。但這大約因為年齡的關係,我卻並未蒙著一毫感化,所以竟完全忘卻了。然而圓規很不平,顯出鄙夷的神色,仿佛嗤笑法國人不知道拿破侖,美國人不知道華盛頓似的,冷笑說:
“忘了?這真是貴人眼高……”
“那有這事……我……”我惶恐著,站起來說。
“那麼,我對你說。迅哥兒,你闊了,搬動又笨重,你還要什麼這些破爛木器,讓我拿去罷。我們小戶人家,用得著。”
“我並沒有闊哩。我須賣了這些,再去……”
“阿呀呀,你放了道台了,還說不闊?你現在有三房姨太太;出門便是八抬的大轎,還說不闊?嚇,什麼都瞞不過我。”
我知道無話可說了,便閉了口,默默的站著。
“阿呀阿呀,真是愈有錢,便愈是一毫不肯放鬆,愈是一毫不肯放鬆,便愈有錢……”圓規一面憤憤的迴轉身,一面絮絮的說,慢慢向外走,順便將我母親的一副手套塞在褲腰裡,出去了。
此後又有近處的本家和親戚來訪問我。我一面應酬,偷空便收拾些行李,這樣的過了三四天。
一日是天氣很冷的午後,我吃過午飯,坐著喝茶,覺得外面有人進來了,便回頭去看。我看時,不由的非常出驚,慌忙站起身,迎著走去。
這來的便是閏土。雖然我一見便知道是閏土,但又不是我這記憶上的閏土了。他身材增加了一倍;先前的紫色的圓臉,已經變作灰黃,而且加上了很深的皺紋;眼睛也像他父親一樣,周圍都腫得通紅,這我知道,在海邊種地的人,終日吹著海風,大抵是這樣的。他頭上是一頂破氈帽,身上只一件極薄的棉衣,渾身瑟索著;手裡提著一個紙包和一支長煙管,那手也不是我所記得的紅活圓實的手,卻又粗又笨而且開裂,像是松樹皮了。
我這時很興奮,但不知道怎麼說才好,只是說:
“阿!閏土哥,——你來了?……”
我接著便有許多話,想要連珠一般湧出:角雞,跳魚兒,貝殼,猹,……但又總覺得被什麼擋著似的,單在腦裡面迴旋,吐不出口外去。
他站住了,臉上現出歡喜和淒涼的神情;動著嘴唇,卻沒有作聲。他的態度終於恭敬起來了,分明的叫道:
“老爺!……”
我似乎打了一個寒噤;我就知道,我們之間已經隔了一層可悲的厚障壁了。我也說不出話。
他回過頭去說,“水生,給老爺磕頭。”便拖出躲在背後的孩子來,這正是一個廿年前的閏土,只是黃瘦些,頸子上沒有銀圈罷了。“這是第五個孩子,沒有見過世面,躲躲閃閃……”
母親和宏兒下樓來了,他們大約也聽到了聲音。
“老太太。信是早收到了。我實在喜歡的不得了,知道老爺回來……”閏土說。
“阿,你怎的這樣客氣起來。你們先前不是哥弟稱呼麽?還是照舊:迅哥兒。”母親高興的說。
“阿呀,老太太真是……這成什麼規矩。那時是孩子,不懂事……”閏土說著,又叫水生上來打拱,那孩子卻害羞,緊緊的只貼在他背後。
“他就是水生?第五個?都是生人,怕生也難怪的;還是宏兒和他去走走。”母親說。
宏兒聽得這話,便來招水生,水生卻鬆鬆爽爽同他一路出去了。母親叫閏土坐,他遲疑了一回,終於就了坐,將長煙管靠在桌旁,遞過紙包來,說:
“冬天沒有什麼東西了。這一點乾青豆倒是自家曬在那裡的,請老爺……”
我問問他的景況。他只是搖頭。
“非常難。第六個孩子也會幫忙了,卻總是吃不夠……又不太平……什麼地方都要錢,沒有規定……收成又壞。種出東西來,挑去賣,總要捐幾回錢,折了本;不去賣,又只能爛掉……”
他只是搖頭;臉上雖然刻著許多皺紋,卻全然不動,仿佛石像一般。他大約只是覺得苦,卻又形容不出,沉默了片時,便拿起煙管來默默的吸煙了。
母親問他,知道他的家裡事務忙,明天便得回去;又沒有吃過午飯,便叫他自己到廚下炒飯吃去。
他出去了;母親和我都嘆息他的景況:多子,饑荒,苛稅,兵,匪,官,紳,都苦得他像一個木偶人了。母親對我說,凡是不必搬走的東西,盡可以送他,可以聽他自己去揀擇。
下午,他揀好了幾件東西:兩條長桌,四個椅子,一副香爐和燭臺,一桿抬秤。他又要所有的草灰(我們這裡煮飯是燒稻草的,那灰,可以做沙地的肥料),待我們啟程的時候,他用船來載去。
夜間,我們又談些閑天,都是無關緊要的話;第二天早晨,他就領了水生回去了。
又過了九日,是我們啟程的日期。閏土早晨便到了,水生沒有同來,卻只帶著一個五歲的女兒管船隻。我們終日很忙碌,再沒有談天的工夫。來客也不少,有送行的,有拿東西的,有送行兼拿東西的。待到傍晚我們上船的時候,這老屋裡的所有破舊大小粗細東西,已經一掃而空了。
我們的船向前走,兩岸的青山在黃昏中,都裝成了深黛顏色,連著退向船後梢去。
宏兒和我靠著船窗,同看外面模糊的風景,他忽然問道:
“大伯!我們什麼時候回來?”
“回來?你怎麼還沒有走就想回來了。”
“可是,水生約我到他家玩去咧……”他睜著大的黑眼睛,癡癡的想。
我和母親也都有些惘然,於是又提起閏土來。母親說,那豆腐西施的楊二嫂,自從我家收拾行李以來,本是每日必到的,前天伊在灰堆裡,掏出十多個碗碟來,議論之後,便定說是閏土埋著的,他可以在運灰的時候,一齊搬回家裡去;楊二嫂發見了這件事,自己很以為功,便拿了那狗氣殺(這是我們這裡養雞的器具,木盤上面有著柵欄,內盛食料,雞可以伸進頸子去啄,狗卻不能,只能看著氣死),飛也似的跑了,虧伊裝著這麼高低的小腳,竟跑得這樣快。
老屋離我愈遠了;故鄉的山水也都漸漸遠離了我,但我卻並不感到怎樣的留戀。我只覺得我四面有看不見的高牆,將我隔成孤身,使我非常氣悶;那西瓜地上的銀項圈的小英雄的影像,我本來十分清楚,現在卻忽地模糊了,又使我非常的悲哀。
母親和宏兒都睡著了。
我躺著,聽船底潺潺的水聲,知道我在走我的路。我想:我竟與閏土隔絕到這地步了,但我們的後輩還是一氣,宏兒不是正在想念水生麽。我希望他們不再像我,又大家隔膜起來……然而我又不願意他們因為要一氣,都如我的辛苦展轉而生活,也不願意他們都如閏土的辛苦麻木而生活,也不願意都如別人的辛苦恣睢而生活。他們應該有新的生活,為我們所未經生活過的。
我想到希望,忽然害怕起來了。閏土要香爐和燭臺的時候,我還暗地裡笑他,以為他總是崇拜偶像,什麼時候都不忘卻。現在我所謂希望,不也是我自己手製的偶像麽?只是他的願望切近,我的願望茫遠罷了。
我在朦朧中,眼前展開一片海邊碧綠的沙地來,上面深藍的天空中掛著一輪金黃的圓月。我想:希望本是無所謂有,無所謂無的。這正如地上的路;其實地上本沒有路,走的人多了,也便成了路。
一九二一年一月

View file

@ -2,9 +2,13 @@
title: 故鄉
published: 1921-01-10
tags: ["鲁迅","经典读本"]
lang: zh
abbrlink: hometown
---
import { Image } from 'astro:assets';
lang: zh 我冒了嚴寒,回到相隔二千餘里,別了二十餘年的故鄉去。
<Image src="https://image.radishzz.cc/picsmaller/03.webp" inferSize alt="这是图片的说明文字" />
我冒了嚴寒,回到相隔二千餘里,別了二十餘年的故鄉去。

View file

@ -46,7 +46,7 @@ const { commentURL = '', imageHostURL = '', customGoogleAnalyticsJS = '', custom
{[locale, ...moreLocale].map(lang => (
<link
rel="alternate"
href={`${url}${lang === locale ? '' : `/${lang}`}`}
href={`${url}${lang === locale ? '' : `/${lang}/`}`}
hreflang={lang === 'zh-tw' ? 'zh-TW' : lang}
/>
))}

View file

@ -21,7 +21,7 @@ const postsByYear = await getPostsByYear(lang)
<ul>
{pinnedPosts.map(post => (
<li>
<a href={`/${lang}/posts/${post.data.slug || post.slug}/`}>
<a href={`/${lang}/posts/${post.data.abbrlink || post.slug}/`}>
{post.data.title}
<time class="block lg:inline lg:before:content-['_']">
{post.data.published.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit' }).replace('/', '-')}
@ -45,7 +45,7 @@ const postsByYear = await getPostsByYear(lang)
// Single Post
<li class="mt-6">
{/* Post Title */}
<a href={`/${lang}/posts/${post.data.slug || post.slug}`}>{post.data.title}</a>
<a href={`/${lang}/posts/${post.data.abbrlink || post.slug}/`}>{post.data.title}</a>
{/* Post Date */}
<time class="block text-5.6 leading-7 font-navbar opacity-30 lg:inline lg:before:content-['_']">
{post.data.published.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit' }).replace('/', '-')}

View file

@ -27,7 +27,7 @@ const allTags = await getAllTags()
<ul>
{posts.map(post => (
<li>
<a href={`/${lang}/posts/${post.data.slug || post.slug}/`}>{post.data.title}</a>
<a href={`/${lang}/posts/${post.data.abbrlink || post.slug}/`}>{post.data.title}</a>
</li>
))}
</ul>

View file

@ -28,7 +28,7 @@ const { Content, remarkPluginFrontmatter } = await post.render()
<article class="heti mb-12.6">
<h1 class="post-title">
<span
transition:name={`post-${post.data.slug || post.slug}`}
transition:name={`post-${post.data.abbrlink || post.slug}`}
data-disable-transition-on-theme
>
{post.data.title}
@ -37,7 +37,7 @@ const { Content, remarkPluginFrontmatter } = await post.render()
<div
class="mb-17 block c-primary font-time"
transition:name={`time-${post.data.slug || post.slug}`}
transition:name={`time-${post.data.abbrlink || post.slug}`}
data-disable-transition-on-theme
>
<time>

View file

@ -25,7 +25,7 @@ export async function checkSlugDuplication(posts: Post[]): Promise<string[]> {
posts.forEach((post) => {
const lang = post.data.lang || ''
const slug = post.data.slug || post.slug
const slug = post.data.abbrlink || post.slug
if (!slugMap.has(lang)) {
slugMap.set(lang, new Set())

View file

@ -20,7 +20,14 @@ export function getLangFromPath(path: string) {
export function getLocalizedPath(path: string, currentLang?: string) {
const clean = cleanPath(path)
const lang = currentLang || getLangFromPath(path)
return lang === defaultLocale ? `/${clean}` : `/${lang}/${clean}`
// 如果是根目录clean为空则返回/
if (clean === '') {
return lang === defaultLocale ? '/' : `/${lang}/`
}
// 其他路径正常处理
return lang === defaultLocale ? `/${clean}/` : `/${lang}/${clean}/`
}
export function isHomePage(path: string) {
@ -55,3 +62,27 @@ export function getPagePath(path: string) {
getLocalizedPath: (targetPath: string) => getLocalizedPath(targetPath, currentLang),
}
}
/**
*
* @param post
* @returns
*/
export function getAvailableLanguages(post?: { data?: { lang?: string } }) {
// 默认支持所有配置的语言
const defaultLangs = ['', ...themeConfig.global.moreLocale]
// 如果没有提供文章对象,返回所有语言
if (!post || !post.data) {
return defaultLangs
}
// 确定文章支持的语言
if (post.data.lang && typeof post.data.lang === 'string' && post.data.lang.trim() !== '') {
// 如果lang是字符串
return ['', post.data.lang]
}
// 如果没有指定或格式不对,返回所有语言
return defaultLangs
}

View file

@ -1,6 +1,9 @@
import type { CollectionEntry } from 'astro:content'
import { themeConfig } from '@/config'
// 获取默认语言
const defaultLocale = themeConfig.global.locale
export function generateLanguagePaths() {
return themeConfig.global.moreLocale.map(lang => ({
params: { lang },
@ -10,29 +13,77 @@ export function generateLanguagePaths() {
export function generatePostPaths(posts: CollectionEntry<'posts'>[]) {
return posts.map(post => ({
params: {
slug: post.data.slug || post.slug,
slug: post.data.abbrlink || post.slug,
},
props: { post },
}))
}
export function generateMultiLangPostPaths(posts: CollectionEntry<'posts'>[]) {
return themeConfig.global.moreLocale.flatMap(lang =>
posts.map(post => ({
params: {
lang,
slug: post.data.slug || post.slug,
},
props: { post },
})),
)
interface PathResult {
params: {
lang: string
slug: string
}
props: {
post: CollectionEntry<'posts'>
}
}
const result: PathResult[] = []
posts.forEach((post) => {
// 确定这篇文章应该生成哪些语言版本
let postLangs: string[] = themeConfig.global.moreLocale
if (post.data.lang && typeof post.data.lang === 'string' && post.data.lang.trim() !== '') {
// 如果lang是单个字符串转为数组
postLangs = [post.data.lang]
}
// 处理非默认语言的路径
postLangs.forEach((lang) => {
// 跳过默认语言,它将通过 generatePostPaths 生成
if (lang !== defaultLocale) {
result.push({
params: {
lang,
slug: post.data.abbrlink || post.slug,
},
props: { post },
})
}
})
// 如果文章支持默认语言,则生成无语言代码的路径
// 默认语言条件未指定lang属性或lang属性等于defaultLocale
const supportsDefaultLang = !post.data.lang
|| (typeof post.data.lang === 'string' && post.data.lang === defaultLocale)
if (supportsDefaultLang) {
// 默认语言的路径不包含语言代码,在这里用特殊参数标记
// 这将由 [slug].astro 页面处理不在URL中显示语言代码
result.push({
params: {
lang: 'default',
slug: post.data.abbrlink || post.slug,
},
props: { post },
})
}
})
return result
}
export function generateMultiLangTagPaths(tags: string[]) {
return themeConfig.global.moreLocale.flatMap(lang =>
tags.map(tag => ({
params: { lang, tag },
props: { tag },
})),
// 跳过默认语言,它将通过其他路径生成
lang !== defaultLocale
? tags.map(tag => ({
params: { lang, tag },
props: { tag },
}))
: [],
)
}

View file

@ -46,7 +46,7 @@ export async function generateRSS({ lang }: GenerateRSSOptions = {}) {
description: post.data.description || getExcerpt(post.body),
// Generate absolute URL with optional language prefix
link: new URL(
`${lang ? `${lang}/` : ''}posts/${post.data.slug || post.slug}/`,
`${lang ? `${lang}/` : ''}posts/${post.data.abbrlink || post.slug}/`,
url,
).toString(),
// Convert markdown content to HTML, allowing img tags