refactor: refactoring multi-language implementation

This commit is contained in:
Guoqi Sun 2024-12-20 21:30:04 +08:00
parent 29a0a19da0
commit f431180328
94 changed files with 382 additions and 4742 deletions

View file

@ -1 +0,0 @@
export * from '@astrojs/netlify/ssr-function.js';

View file

@ -1,280 +0,0 @@
import { c as cookieExports } from './chunks/index_BDj_P1f5.mjs';
import { A as AstroError, R as ResponseSentError, F as ForbiddenRewrite } from './chunks/astro/server_CzcTbIe6.mjs';
const DELETED_EXPIRATION = /* @__PURE__ */ new Date(0);
const DELETED_VALUE = "deleted";
const responseSentSymbol = Symbol.for("astro.responseSent");
class AstroCookie {
constructor(value) {
this.value = value;
}
json() {
if (this.value === void 0) {
throw new Error(`Cannot convert undefined to an object.`);
}
return JSON.parse(this.value);
}
number() {
return Number(this.value);
}
boolean() {
if (this.value === "false") return false;
if (this.value === "0") return false;
return Boolean(this.value);
}
}
class AstroCookies {
#request;
#requestValues;
#outgoing;
#consumed;
constructor(request) {
this.#request = request;
this.#requestValues = null;
this.#outgoing = null;
this.#consumed = false;
}
/**
* Astro.cookies.delete(key) is used to delete a cookie. Using this method will result
* in a Set-Cookie header added to the response.
* @param key The cookie to delete
* @param options Options related to this deletion, such as the path of the cookie.
*/
delete(key, options) {
const {
// @ts-expect-error
maxAge: _ignoredMaxAge,
// @ts-expect-error
expires: _ignoredExpires,
...sanitizedOptions
} = options || {};
const serializeOptions = {
expires: DELETED_EXPIRATION,
...sanitizedOptions
};
this.#ensureOutgoingMap().set(key, [
DELETED_VALUE,
cookieExports.serialize(key, DELETED_VALUE, serializeOptions),
false
]);
}
/**
* Astro.cookies.get(key) is used to get a cookie value. The cookie value is read from the
* request. If you have set a cookie via Astro.cookies.set(key, value), the value will be taken
* from that set call, overriding any values already part of the request.
* @param key The cookie to get.
* @returns An object containing the cookie value as well as convenience methods for converting its value.
*/
get(key, options = void 0) {
if (this.#outgoing?.has(key)) {
let [serializedValue, , isSetValue] = this.#outgoing.get(key);
if (isSetValue) {
return new AstroCookie(serializedValue);
} else {
return void 0;
}
}
const values = this.#ensureParsed(options);
if (key in values) {
const value = values[key];
return new AstroCookie(value);
}
}
/**
* Astro.cookies.has(key) returns a boolean indicating whether this cookie is either
* part of the initial request or set via Astro.cookies.set(key)
* @param key The cookie to check for.
* @returns
*/
has(key, options = void 0) {
if (this.#outgoing?.has(key)) {
let [, , isSetValue] = this.#outgoing.get(key);
return isSetValue;
}
const values = this.#ensureParsed(options);
return !!values[key];
}
/**
* Astro.cookies.set(key, value) is used to set a cookie's value. If provided
* an object it will be stringified via JSON.stringify(value). Additionally you
* can provide options customizing how this cookie will be set, such as setting httpOnly
* in order to prevent the cookie from being read in client-side JavaScript.
* @param key The name of the cookie to set.
* @param value A value, either a string or other primitive or an object.
* @param options Options for the cookie, such as the path and security settings.
*/
set(key, value, options) {
if (this.#consumed) {
const warning = new Error(
"Astro.cookies.set() was called after the cookies had already been sent to the browser.\nThis may have happened if this method was called in an imported component.\nPlease make sure that Astro.cookies.set() is only called in the frontmatter of the main page."
);
warning.name = "Warning";
console.warn(warning);
}
let serializedValue;
if (typeof value === "string") {
serializedValue = value;
} else {
let toStringValue = value.toString();
if (toStringValue === Object.prototype.toString.call(value)) {
serializedValue = JSON.stringify(value);
} else {
serializedValue = toStringValue;
}
}
const serializeOptions = {};
if (options) {
Object.assign(serializeOptions, options);
}
this.#ensureOutgoingMap().set(key, [
serializedValue,
cookieExports.serialize(key, serializedValue, serializeOptions),
true
]);
if (this.#request[responseSentSymbol]) {
throw new AstroError({
...ResponseSentError
});
}
}
/**
* Merges a new AstroCookies instance into the current instance. Any new cookies
* will be added to the current instance, overwriting any existing cookies with the same name.
*/
merge(cookies) {
const outgoing = cookies.#outgoing;
if (outgoing) {
for (const [key, value] of outgoing) {
this.#ensureOutgoingMap().set(key, value);
}
}
}
/**
* Astro.cookies.header() returns an iterator for the cookies that have previously
* been set by either Astro.cookies.set() or Astro.cookies.delete().
* This method is primarily used by adapters to set the header on outgoing responses.
* @returns
*/
*headers() {
if (this.#outgoing == null) return;
for (const [, value] of this.#outgoing) {
yield value[1];
}
}
/**
* Behaves the same as AstroCookies.prototype.headers(),
* but allows a warning when cookies are set after the instance is consumed.
*/
static consume(cookies) {
cookies.#consumed = true;
return cookies.headers();
}
#ensureParsed(options = void 0) {
if (!this.#requestValues) {
this.#parse(options);
}
if (!this.#requestValues) {
this.#requestValues = {};
}
return this.#requestValues;
}
#ensureOutgoingMap() {
if (!this.#outgoing) {
this.#outgoing = /* @__PURE__ */ new Map();
}
return this.#outgoing;
}
#parse(options = void 0) {
const raw = this.#request.headers.get("cookie");
if (!raw) {
return;
}
this.#requestValues = cookieExports.parse(raw, options);
}
}
function getParams(route, pathname) {
if (!route.params.length) return {};
const paramsMatch = route.pattern.exec(pathname);
if (!paramsMatch) return {};
const params = {};
route.params.forEach((key, i) => {
if (key.startsWith("...")) {
params[key.slice(3)] = paramsMatch[i + 1] ? paramsMatch[i + 1] : void 0;
} else {
params[key] = paramsMatch[i + 1];
}
});
return params;
}
const apiContextRoutesSymbol = Symbol.for("context.routes");
function sequence(...handlers) {
const filtered = handlers.filter((h) => !!h);
const length = filtered.length;
if (!length) {
return defineMiddleware((_context, next) => {
return next();
});
}
return defineMiddleware((context, next) => {
let carriedPayload = void 0;
return applyHandle(0, context);
function applyHandle(i, handleContext) {
const handle = filtered[i];
const result = handle(handleContext, async (payload) => {
if (i < length - 1) {
if (payload) {
let newRequest;
if (payload instanceof Request) {
newRequest = payload;
} else if (payload instanceof URL) {
newRequest = new Request(payload, handleContext.request);
} else {
newRequest = new Request(
new URL(payload, handleContext.url.origin),
handleContext.request
);
}
const pipeline = Reflect.get(handleContext, apiContextRoutesSymbol);
const { routeData, pathname } = await pipeline.tryRewrite(
payload,
handleContext.request
);
if (pipeline.serverLike === true && handleContext.isPrerendered === false && routeData.prerender === true) {
throw new AstroError({
...ForbiddenRewrite,
message: ForbiddenRewrite.message(pathname, pathname, routeData.component),
hint: ForbiddenRewrite.hint(routeData.component)
});
}
carriedPayload = payload;
handleContext.request = newRequest;
handleContext.url = new URL(newRequest.url);
handleContext.cookies = new AstroCookies(newRequest);
handleContext.params = getParams(routeData, pathname);
}
return applyHandle(i + 1, handleContext);
} else {
return next(payload ?? carriedPayload);
}
});
return result;
}
});
}
function defineMiddleware(fn) {
return fn;
}
var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});function removeTrailingSlash(url){return url.at(-1)==="/"?url.slice(0,-1):url}__name(removeTrailingSlash,"removeTrailingSlash");function resolveTrailingSlash(url){let pathName=typeof url=="string"?url:url.pathname;return pathName!=="/"&&pathName.at(-1)==="/"&&(pathName=pathName.slice(0,-1)),pathName}__name(resolveTrailingSlash,"resolveTrailingSlash");function removeHtmlExtension(url){return url.endsWith(".html")?url.slice(0,-5):url}__name(removeHtmlExtension,"removeHtmlExtension");var i18nMiddleware=defineMiddleware((context,next)=>{return next();});var onRequest$1=i18nMiddleware;
const onRequest = sequence(
onRequest$1,
);
export { onRequest };

View file

@ -1,25 +0,0 @@
import * as ssrFunction_js from '@astrojs/netlify/ssr-function.js';
function _mergeNamespaces(n, m) {
for (var i = 0; i < m.length; i++) {
const e = m[i];
if (typeof e !== 'string' && !Array.isArray(e)) { for (const k in e) {
if (k !== 'default' && !(k in n)) {
const d = Object.getOwnPropertyDescriptor(e, k);
if (d) {
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: () => e[k]
});
}
}
} }
}
return Object.freeze(Object.defineProperty(n, Symbol.toStringTag, { value: 'Module' }));
}
const serverEntrypointModule = /*#__PURE__*/_mergeNamespaces({
__proto__: null
}, [ssrFunction_js]);
export { serverEntrypointModule as s };

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1,32 +0,0 @@
import { createElement, Fragment } from 'react';
var R=0,k=1,j=2;var D=new Set(["area","base","br","col","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"]),x=new Set(["script","style"]),_=/([\@\.a-z0-9_\:\-]*)\s*?=?\s*?(['"]?)([\s\S]*?)\2\s+/gim,o=/(?:<(\/?)([a-zA-Z][a-zA-Z0-9\:-]*)(?:\s([^>]*?))?((?:\s*\/)?)>|(<\!\-\-)([\s\S]*?)(\-\->)|(<\!)([\s\S]*?)(>))/gm;function P(e){let t={},a;if(e)for(_.lastIndex=0,e=" "+(e||"")+" ";a=_.exec(e);)a[0]!==" "&&(t[a[1]]=a[3]);return t}function w(e){let t=typeof e=="string"?e:e.value,a,r,n,i,l,d,g,h,s,c=[];o.lastIndex=0,r=a={type:0,children:[]};let E=0;function m(){i=t.substring(E,o.lastIndex-n[0].length),i&&r.children.push({type:2,value:i,parent:r});}for(;n=o.exec(t);){if(d=n[5]||n[8],g=n[6]||n[9],h=n[7]||n[10],x.has(r.name)&&n[2]!==r.name){l=o.lastIndex-n[0].length,r.children.length>0&&(r.children[0].value+=n[0]);continue}else if(d==="<!--"){if(l=o.lastIndex-n[0].length,x.has(r.name))continue;s={type:3,value:g,parent:r,loc:[{start:l,end:l+d.length},{start:o.lastIndex-h.length,end:o.lastIndex}]},c.push(s),s.parent.children.push(s);}else if(d==="<!")l=o.lastIndex-n[0].length,s={type:4,value:g,parent:r,loc:[{start:l,end:l+d.length},{start:o.lastIndex-h.length,end:o.lastIndex}]},c.push(s),s.parent.children.push(s);else if(n[1]!=="/")if(m(),x.has(r.name)){E=o.lastIndex,m();continue}else s={type:1,name:n[2]+"",attributes:P(n[3]),parent:r,children:[],loc:[{start:o.lastIndex-n[0].length,end:o.lastIndex}]},c.push(s),s.parent.children.push(s),n[4]&&n[4].indexOf("/")>-1||D.has(s.name)?(s.loc[1]=s.loc[0],s.isSelfClosingTag=!0):r=s;else m(),n[2]+""===r.name?(s=r,r=s.parent,s.loc.push({start:o.lastIndex-n[0].length,end:o.lastIndex}),i=t.substring(s.loc[0].end,s.loc[1].start),s.children.length===0&&s.children.push({type:2,value:i,parent:r})):n[2]+""===c[c.length-1].name&&c[c.length-1].isSelfClosingTag===!0&&(s=c[c.length-1],s.loc.push({start:o.lastIndex-n[0].length,end:o.lastIndex}));E=o.lastIndex;}return i=t.slice(E),r.children.push({type:2,value:i,parent:r}),a}
let ids = 0;
function convert(children) {
let doc = w(children.toString().trim());
let id = ids++;
let key = 0;
function createReactElementFromNode(node) {
const childVnodes =
Array.isArray(node.children) && node.children.length
? node.children.map((child) => createReactElementFromNode(child)).filter(Boolean)
: undefined;
if (node.type === R) {
return createElement(Fragment, {}, childVnodes);
} else if (node.type === k) {
const { class: className, ...props } = node.attributes;
return createElement(node.name, { ...props, className, key: `${id}-${key++}` }, childVnodes);
} else if (node.type === j) {
// 0-length text gets omitted in JSX
return node.value.trim() ? node.value : undefined;
}
}
const root = createReactElementFromNode(doc);
return root.props.children;
}
export { convert as default };

View file

@ -1,72 +0,0 @@
import { renderers } from './renderers.mjs';
import { s as serverEntrypointModule } from './chunks/_@astrojs-ssr-adapter_CvSoi7hX.mjs';
import { manifest } from './manifest_DsyWDHzC.mjs';
import { createExports } from '@astrojs/netlify/ssr-function.js';
const serverIslandMap = new Map([
]);;
const _page0 = () => import('./pages/about.astro.mjs');
const _page1 = () => import('./pages/archive.astro.mjs');
const _page2 = () => import('./pages/atom.xml.astro.mjs');
const _page3 = () => import('./pages/en/about.astro.mjs');
const _page4 = () => import('./pages/en/archive.astro.mjs');
const _page5 = () => import('./pages/en/en/posts/_---slug_.astro.mjs');
const _page6 = () => import('./pages/en/en/tags/_tag_.astro.mjs');
const _page7 = () => import('./pages/en/posts/_---slug_.astro.mjs');
const _page8 = () => import('./pages/en/posts/_---slug_.astro2.mjs');
const _page9 = () => import('./pages/en/tags/_tag_.astro.mjs');
const _page10 = () => import('./pages/en/tags/_tag_.astro2.mjs');
const _page11 = () => import('./pages/en/tags.astro.mjs');
const _page12 = () => import('./pages/en/zh/posts/_---slug_.astro.mjs');
const _page13 = () => import('./pages/en/zh/tags/_tag_.astro.mjs');
const _page14 = () => import('./pages/en.astro.mjs');
const _page15 = () => import('./pages/posts/_---slug_.astro.mjs');
const _page16 = () => import('./pages/robots.txt.astro.mjs');
const _page17 = () => import('./pages/tags/_tag_.astro.mjs');
const _page18 = () => import('./pages/tags.astro.mjs');
const _page19 = () => import('./pages/zh/posts/_---slug_.astro.mjs');
const _page20 = () => import('./pages/zh/tags/_tag_.astro.mjs');
const _page21 = () => import('./pages/index.astro.mjs');
const pageMap = new Map([
["src/pages/about/index.astro", _page0],
["src/pages/archive/index.astro", _page1],
["src/pages/atom/index.xml.ts", _page2],
["src/astro_tmp_pages_en/about/index.astro", _page3],
["src/astro_tmp_pages_en/archive/index.astro", _page4],
["src/astro_tmp_pages_en/en/posts/[...slug].astro", _page5],
["src/astro_tmp_pages_en/en/tags/[tag].astro", _page6],
["src/pages/en/posts/[...slug].astro", _page7],
["src/astro_tmp_pages_en/posts/[...slug].astro", _page8],
["src/pages/en/tags/[tag].astro", _page9],
["src/astro_tmp_pages_en/tags/[tag].astro", _page10],
["src/astro_tmp_pages_en/tags/index.astro", _page11],
["src/astro_tmp_pages_en/zh/posts/[...slug].astro", _page12],
["src/astro_tmp_pages_en/zh/tags/[tag].astro", _page13],
["src/astro_tmp_pages_en/index.astro", _page14],
["src/pages/posts/[...slug].astro", _page15],
["src/pages/robots/index.txt.ts", _page16],
["src/pages/tags/[tag].astro", _page17],
["src/pages/tags/index.astro", _page18],
["src/pages/zh/posts/[...slug].astro", _page19],
["src/pages/zh/tags/[tag].astro", _page20],
["src/pages/index.astro", _page21]
]);
const _manifest = Object.assign(manifest, {
pageMap,
serverIslandMap,
renderers,
middleware: () => import('./_astro-internal_middleware.mjs')
});
const _args = {
"middlewareSecret": "b398f6c4-5e35-4573-8803-aeedff401909"
};
const _exports = createExports(_manifest, _args);
const __astrojsSsrVirtualEntry = _exports.default;
const _start = 'start';
if (_start in serverEntrypointModule) {
serverEntrypointModule[_start](_manifest, _args);
}
export { __astrojsSsrVirtualEntry as default, pageMap };

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1 +0,0 @@
// Contents removed by Astro as it's used for prerendering only

View file

@ -1,310 +0,0 @@
import React, { createElement } from 'react';
import ReactDOM from 'react-dom/server';
import { p as AstroJSX, q as renderJSX, f as createVNode, s as AstroUserError } from './chunks/astro/server_CzcTbIe6.mjs';
const opts = {
experimentalReactChildren: false
};
const contexts = new WeakMap();
const ID_PREFIX = 'r';
function getContext(rendererContextResult) {
if (contexts.has(rendererContextResult)) {
return contexts.get(rendererContextResult);
}
const ctx = {
currentIndex: 0,
get id() {
return ID_PREFIX + this.currentIndex.toString();
},
};
contexts.set(rendererContextResult, ctx);
return ctx;
}
function incrementId(rendererContextResult) {
const ctx = getContext(rendererContextResult);
const id = ctx.id;
ctx.currentIndex++;
return id;
}
/**
* Astro passes `children` as a string of HTML, so we need
* a wrapper `div` to render that content as VNodes.
*
* As a bonus, we can signal to React that this subtree is
* entirely static and will never change via `shouldComponentUpdate`.
*/
const StaticHtml = ({ value, name, hydrate = true }) => {
if (!value) return null;
const tagName = hydrate ? 'astro-slot' : 'astro-static-slot';
return createElement(tagName, {
name,
suppressHydrationWarning: true,
dangerouslySetInnerHTML: { __html: value },
});
};
/**
* This tells React to opt-out of re-rendering this subtree,
* In addition to being a performance optimization,
* this also allows other frameworks to attach to `children`.
*
* See https://preactjs.com/guide/v8/external-dom-mutations
*/
StaticHtml.shouldComponentUpdate = () => false;
const slotName$1 = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
const reactTypeof = Symbol.for('react.element');
async function check$1(Component, props, children) {
// Note: there are packages that do some unholy things to create "components".
// Checking the $$typeof property catches most of these patterns.
if (typeof Component === 'object') {
return Component['$$typeof'].toString().slice('Symbol('.length).startsWith('react');
}
if (typeof Component !== 'function') return false;
if (Component.name === 'QwikComponent') return false;
// Preact forwarded-ref components can be functions, which React does not support
if (typeof Component === 'function' && Component['$$typeof'] === Symbol.for('react.forward_ref'))
return false;
if (Component.prototype != null && typeof Component.prototype.render === 'function') {
return React.Component.isPrototypeOf(Component) || React.PureComponent.isPrototypeOf(Component);
}
let isReactComponent = false;
function Tester(...args) {
try {
const vnode = Component(...args);
if (vnode && vnode['$$typeof'] === reactTypeof) {
isReactComponent = true;
}
} catch {}
return React.createElement('div');
}
await renderToStaticMarkup$1(Tester, props, children, {});
return isReactComponent;
}
async function getNodeWritable() {
let nodeStreamBuiltinModuleName = 'node:stream';
let { Writable } = await import(/* @vite-ignore */ nodeStreamBuiltinModuleName);
return Writable;
}
function needsHydration(metadata) {
// Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot`
return metadata.astroStaticSlot ? !!metadata.hydrate : true;
}
async function renderToStaticMarkup$1(Component, props, { default: children, ...slotted }, metadata) {
let prefix;
if (this && this.result) {
prefix = incrementId(this.result);
}
const attrs = { prefix };
delete props['class'];
const slots = {};
for (const [key, value] of Object.entries(slotted)) {
const name = slotName$1(key);
slots[name] = React.createElement(StaticHtml, {
hydrate: needsHydration(metadata),
value,
name,
});
}
// Note: create newProps to avoid mutating `props` before they are serialized
const newProps = {
...props,
...slots,
};
const newChildren = children ?? props.children;
if (children && opts.experimentalReactChildren) {
attrs['data-react-children'] = true;
const convert = await import('./chunks/vnode-children_C1YIWAGb.mjs').then((mod) => mod.default);
newProps.children = convert(children);
} else if (newChildren != null) {
newProps.children = React.createElement(StaticHtml, {
hydrate: needsHydration(metadata),
value: newChildren,
});
}
const formState = this ? await getFormState(this) : undefined;
if (formState) {
attrs['data-action-result'] = JSON.stringify(formState[0]);
attrs['data-action-key'] = formState[1];
attrs['data-action-name'] = formState[2];
}
const vnode = React.createElement(Component, newProps);
const renderOptions = {
identifierPrefix: prefix,
formState,
};
let html;
if ('renderToReadableStream' in ReactDOM) {
html = await renderToReadableStreamAsync(vnode, renderOptions);
} else {
html = await renderToPipeableStreamAsync(vnode, renderOptions);
}
return { html, attrs };
}
/**
* @returns {Promise<[actionResult: any, actionKey: string, actionName: string] | undefined>}
*/
async function getFormState({ result }) {
const { request, actionResult } = result;
if (!actionResult) return undefined;
if (!isFormRequest(request.headers.get('content-type'))) return undefined;
const { searchParams } = new URL(request.url);
const formData = await request.clone().formData();
/**
* The key generated by React to identify each `useActionState()` call.
* @example "k511f74df5a35d32e7cf266450d85cb6c"
*/
const actionKey = formData.get('$ACTION_KEY')?.toString();
/**
* The action name returned by an action's `toString()` property.
* This matches the endpoint path.
* @example "/_actions/blog.like"
*/
const actionName = searchParams.get('_action');
if (!actionKey || !actionName) return undefined;
return [actionResult, actionKey, actionName];
}
async function renderToPipeableStreamAsync(vnode, options) {
const Writable = await getNodeWritable();
let html = '';
return new Promise((resolve, reject) => {
let error = undefined;
let stream = ReactDOM.renderToPipeableStream(vnode, {
...options,
onError(err) {
error = err;
reject(error);
},
onAllReady() {
stream.pipe(
new Writable({
write(chunk, _encoding, callback) {
html += chunk.toString('utf-8');
callback();
},
destroy() {
resolve(html);
},
}),
);
},
});
});
}
/**
* Use a while loop instead of "for await" due to cloudflare and Vercel Edge issues
* See https://github.com/facebook/react/issues/24169
*/
async function readResult(stream) {
const reader = stream.getReader();
let result = '';
const decoder = new TextDecoder('utf-8');
while (true) {
const { done, value } = await reader.read();
if (done) {
if (value) {
result += decoder.decode(value);
} else {
// This closes the decoder
decoder.decode(new Uint8Array());
}
return result;
}
result += decoder.decode(value, { stream: true });
}
}
async function renderToReadableStreamAsync(vnode, options) {
return await readResult(await ReactDOM.renderToReadableStream(vnode, options));
}
const formContentTypes = ['application/x-www-form-urlencoded', 'multipart/form-data'];
function isFormRequest(contentType) {
// Split off parameters like charset or boundary
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type#content-type_in_html_forms
const type = contentType?.split(';')[0].toLowerCase();
return formContentTypes.some((t) => type === t);
}
const _renderer0 = {
name: '@astrojs/react',
check: check$1,
renderToStaticMarkup: renderToStaticMarkup$1,
supportsAstroStaticSlot: true,
};
const slotName = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
async function check(Component, props, { default: children = null, ...slotted } = {}) {
if (typeof Component !== "function") return false;
const slots = {};
for (const [key, value] of Object.entries(slotted)) {
const name = slotName(key);
slots[name] = value;
}
try {
const result = await Component({ ...props, ...slots, children });
return result[AstroJSX];
} catch (e) {
throwEnhancedErrorIfMdxComponent(e, Component);
}
return false;
}
async function renderToStaticMarkup(Component, props = {}, { default: children = null, ...slotted } = {}) {
const slots = {};
for (const [key, value] of Object.entries(slotted)) {
const name = slotName(key);
slots[name] = value;
}
const { result } = this;
try {
const html = await renderJSX(result, createVNode(Component, { ...props, ...slots, children }));
return { html };
} catch (e) {
throwEnhancedErrorIfMdxComponent(e, Component);
throw e;
}
}
function throwEnhancedErrorIfMdxComponent(error, Component) {
if (Component[Symbol.for("mdx-component")]) {
if (AstroUserError.is(error)) return;
error.title = error.name;
error.hint = `This issue often occurs when your MDX component encounters runtime errors.`;
throw error;
}
}
const renderer = {
name: "astro:jsx",
check,
renderToStaticMarkup
};
var server_default = renderer;
const renderers = [Object.assign({"name":"@astrojs/react","clientEntrypoint":"@astrojs/react/client.js","serverEntrypoint":"@astrojs/react/server.js"}, { ssr: _renderer0 }),Object.assign({"name":"astro:jsx","serverEntrypoint":"file:///Users/sunguoqi/Developer/astro-air/node_modules/.pnpm/@astrojs+mdx@4.0.2_astro@5.0.5_@types+node@22.10.2_jiti@1.21.6_rollup@4.27.4_typescript@5.7.2_yaml@2.6.1_/node_modules/@astrojs/mdx/dist/server.js"}, { ssr: server_default }),];
export { renderers };

View file

@ -1,9 +0,0 @@
{
"images": { "remote_images": [] },
"headers": [
{
"for": "/_astro/*",
"values": { "Cache-Control": "public, max-age=31536000, immutable" }
}
]
}

View file

@ -1,50 +1,15 @@
// @ts-check
// @ts-check
import mdx from "@astrojs/mdx" import mdx from "@astrojs/mdx"
import netlify from "@astrojs/netlify"
import react from "@astrojs/react" import react from "@astrojs/react"
import sitemap from "@astrojs/sitemap"
import tailwind from "@astrojs/tailwind" import tailwind from "@astrojs/tailwind"
import { filterSitemapByDefaultLocale, i18n } from "astro-i18n-aut/integration"
import { defineConfig } from "astro/config" import { defineConfig } from "astro/config"
import { defaultLanguage } from "./src/config"
const defaultLocale = defaultLanguage
const locales = {
en: "en-US",
zh: "zh-CN",
}
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
site: "https://localhost:4321/", site: "https://localhost:4321/",
trailingSlash: "never",
build: {
format: "file",
},
vite: { vite: {
worker: { worker: {
plugins: () => [], plugins: () => [],
}, },
}, },
integrations: [ integrations: [react(), tailwind(), mdx()],
react(),
tailwind(),
mdx(),
i18n({
locales,
defaultLocale,
redirectDefaultLocale: false,
exclude: ["**/atom/**", "**/robots/**", "**/*.xml.*", "**/*.txt.*"],
}),
sitemap({
i18n: {
locales,
defaultLocale,
},
filter: filterSitemapByDefaultLocale({ defaultLocale }),
}),
],
adapter: process.env.NODE_ENV === "production" ? netlify() : undefined,
}) })

View file

@ -13,7 +13,6 @@
"dependencies": { "dependencies": {
"@astrojs/check": "^0.9.4", "@astrojs/check": "^0.9.4",
"@astrojs/mdx": "^4.0.2", "@astrojs/mdx": "^4.0.2",
"@astrojs/netlify": "^6.0.1",
"@astrojs/react": "^4.1.0", "@astrojs/react": "^4.1.0",
"@astrojs/rss": "^4.0.10", "@astrojs/rss": "^4.0.10",
"@astrojs/sitemap": "^3.2.1", "@astrojs/sitemap": "^3.2.1",
@ -21,7 +20,6 @@
"@types/react": "^18.3.12", "@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1", "@types/react-dom": "^18.3.1",
"astro": "^5.1.0", "astro": "^5.1.0",
"astro-i18n-aut": "^0.7.0",
"lucide-react": "^0.468.0", "lucide-react": "^0.468.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",

521
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,30 +1,19 @@
--- ---
import { getLocale } from "astro-i18n-aut" import { latestPosts } from "~/config"
import { defaultLanguage, en, zh } from "~/config" import { getLangFromUrl, useTranslations } from "~/i18n/utils"
import { formatDate, getPostsByLocale } from "~/utils" import { formatDate, getPostsByLocale } from "~/utils"
const locale = getLocale(Astro.url) const lang = getLangFromUrl(Astro.url)
const config = locale === "zh" ? zh : en const t = useTranslations(lang)
const posts = (await getPostsByLocale(locale)).slice(0, config.latestPosts ?? 8) const allPosts = await getPostsByLocale(lang)
const recentPosts = locale === "zh" ? "近期文章" : "Recent Posts" const posts = allPosts.slice(0, latestPosts)
--- ---
<div class="my-8 text-xl font-medium md:my-8">{recentPosts}</div> <div class="my-8 text-xl font-medium md:my-8">{t("blog.latest")}</div>
{ {
posts.map((post: any) => ( posts.map((post: any) => (
<a <a href={`/${lang}/posts/${post.id}/`} class="visited:text-purple-500/90">
href={
defaultLanguage === "zh"
? locale === "zh"
? `/posts/${post.id}/`
: `/en/posts/${post.id}/`
: locale === "en"
? `/posts/${post.id}/`
: `/zn/posts/${post.id}/`
}
class="visited:text-purple-500/90"
>
<div class="my-2 flex text-lg"> <div class="my-2 flex text-lg">
<p class="flex w-36 flex-shrink-0 truncate text-gray-500 dark:text-gray-400"> <p class="flex w-36 flex-shrink-0 truncate text-gray-500 dark:text-gray-400">
<time>{formatDate(post.data.pubDate)}</time> <time>{formatDate(post.data.pubDate)}</time>

View file

@ -1,18 +1,29 @@
---
const currentYear = new Date().getFullYear()
---
<footer class="my-10 flex flex-wrap items-center gap-1"> <footer class="my-10 flex flex-wrap items-center gap-1">
<p> <p>&copy; {currentYear}</p>
&copy; {new Date().getFullYear()}
</p> {
<p class="mx-1">|</p> [
<p>Designed By</p> { text: "Designed By", link: "https://sunguoqi.com", label: "Guoqi Sun" },
<a href="https://sunguoqi.com" class="text-blue-500 hover:underline"> {
Guoqi Sun text: "Powered By",
</a> link: "https://github.com/sun0225SUN/astro-air",
<p class="mx-1">|</p> label: "Astro Air",
<p>Powered By</p> },
<a ].map((item, index) => (
href="https://github.com/sun0225SUN/astro-air" <>
class="text-blue-500 hover:underline" {index > 0 && <p class="mx-1">|</p>}
> <p>{item.text}</p>
Astro Air <a
</a> href={item.link}
class="text-blue-500 transition-colors duration-200 hover:text-blue-700"
>
{item.label}
</a>
</>
))
}
</footer> </footer>

View file

@ -1,26 +1,19 @@
--- ---
import { getLocale } from "astro-i18n-aut"
import { Rss } from "lucide-react" import { Rss } from "lucide-react"
import ThemeToggle from "~/components/astro/theme-toggle.astro" import ThemeToggle from "~/components/astro/theme-toggle.astro"
import { LanguageToggle } from "~/components/react/language-toggle" import { LanguageToggle } from "~/components/react/language-toggle"
import { defaultLanguage, en, zh } from "~/config" import { en, zh } from "~/config"
import { getLangFromUrl } from "~/i18n/utils"
const locale = getLocale(Astro.url) const lang = getLangFromUrl(Astro.url)
const config = locale === "zh" ? zh : en const config = lang === "zh" ? zh : en
--- ---
<header class="flex h-24 w-full items-center justify-between"> <header class="flex h-24 w-full items-center justify-between">
<a <a href="/">
href={defaultLanguage === "zh"
? locale === "zh"
? "/"
: "/en"
: locale === "en"
? "/"
: "/zh"}
>
<div class="text-2xl font-semibold">{config.siteName}</div> <div class="text-2xl font-semibold">{config.siteName}</div>
</a> </a>
<div class="flex items-center gap-6"> <div class="flex items-center gap-6">
{ {
config.rss && ( config.rss && (
@ -37,7 +30,7 @@ const config = locale === "zh" ? zh : en
)) ))
} }
<LanguageToggle currentLocale={locale} client:load /> <LanguageToggle client:load />
<ThemeToggle /> <ThemeToggle />
</div> </div>
</header> </header>

View file

@ -1,11 +1,10 @@
--- ---
import { getLocale } from "astro-i18n-aut" import IntroContentEn from "~/config/en/intro.mdx"
import IntroContentEn from "~/content/en/intro.mdx" import IntroContentZh from "~/config/zh/intro.mdx"
import IntroContentZh from "~/content/zh/intro.mdx" import { getLangFromUrl } from "~/i18n/utils"
const locale = getLocale(Astro.url) const lang = getLangFromUrl(Astro.url)
const IntroContent = lang === "zh" ? IntroContentZh : IntroContentEn
const IntroContent = locale === "zh" ? IntroContentZh : IntroContentEn
--- ---
<div class="my-10"> <div class="my-10">

View file

@ -0,0 +1,61 @@
---
import { en, zh } from "~/config"
import { getLangFromUrl, useTranslations } from "~/i18n/utils"
const lang = getLangFromUrl(Astro.url)
const t = useTranslations(lang)
const { home, archive, tags, custom, about } =
lang === "zh" ? zh.navigation : en.navigation
---
<div class="mb-10 mt-4 flex gap-4 overflow-x-auto whitespace-nowrap text-lg">
{
home && (
<a href={`/${lang}`} class="hover:underline hover:underline-offset-4">
<p>{t("nav.home")}</p>
</a>
)
}
{
archive && (
<a
href={`/${lang}/archive`}
class="hover:underline hover:underline-offset-4"
>
<p>{t("nav.archive")}</p>
</a>
)
}
{
tags && (
<a
href={`/${lang}/tags`}
class="hover:underline hover:underline-offset-4"
>
<p>{t("nav.tags")}</p>
</a>
)
}
{
custom?.map((tab) => (
<a
href={tab.link}
class="hover:underline hover:underline-offset-4"
target="_blank"
>
<p>{tab.label}</p>
</a>
))
}
{
about && (
<a
href={`/${lang}/about`}
class="hover:underline hover:underline-offset-4"
>
<p>{t("nav.about")}</p>
</a>
)
}
</div>

View file

@ -1,120 +0,0 @@
---
import { getLocale } from "astro-i18n-aut"
import { defaultLanguage, en, zh } from "~/config"
const locale = getLocale(Astro.url)
const { home, archive, tags, custom, about } =
locale === "zh" ? zh.navigation : en.navigation
let navList: {
home: string
archive: string
tags: string
about: string
}
switch (locale) {
case "zh":
navList = {
home: "首页",
archive: "归档",
tags: "标签",
about: "关于",
}
break
default:
navList = {
home: "Home",
archive: "Archive",
tags: "Tags",
about: "About",
}
}
---
<div class="mb-10 mt-4 flex gap-4 overflow-x-auto whitespace-nowrap text-lg">
{
home && (
<a
href={
defaultLanguage === "zh"
? locale === "zh"
? "/"
: "/en"
: locale === "en"
? "/"
: "/zh"
}
class="hover:underline hover:underline-offset-4"
>
<p>{navList.home}</p>
</a>
)
}
{
archive && (
<a
href={
defaultLanguage === "zh"
? locale === "zh"
? "/archive"
: "/en/archive"
: locale === "en"
? `/archive`
: `/zh/archive`
}
class="hover:underline hover:underline-offset-4"
>
<p>{navList.archive}</p>
</a>
)
}
{
tags && (
<a
href={
defaultLanguage === "zh"
? locale === "zh"
? "/tags"
: "/en/tags"
: locale === "en"
? `/tags`
: `/zh/tags`
}
class="hover:underline hover:underline-offset-4"
>
<p>{navList.tags}</p>
</a>
)
}
{
custom?.map((tab) => (
<a
href={tab.link}
class="hover:underline hover:underline-offset-4"
target="_blank"
>
<p>{tab.label}</p>
</a>
))
}
{
about && (
<a
href={
defaultLanguage === "zh"
? locale === "zh"
? "/about"
: "/en/about"
: locale === "en"
? `/about`
: `/zh/about`
}
class="hover:underline hover:underline-offset-4"
>
<p>{navList.about}</p>
</a>
)
}
</div>

View file

@ -1,67 +1,46 @@
--- ---
import { defaultLanguage } from "~/config" import { getLangFromUrl, useTranslations } from "~/i18n/utils"
import HomeLayout from "~/layouts/home.astro" import HomeLayout from "~/layouts/home.astro"
import { formatDate } from "~/utils" import { formatDate } from "~/utils"
interface Props { interface Props {
posts: any[] posts: any[]
tag: string tag: string
locale?: string
} }
const { posts, tag, locale = "en" } = Astro.props const { posts, tag } = Astro.props
const lang = getLangFromUrl(Astro.url)
const t = useTranslations(lang)
const filteredPosts = posts.filter((post: any) => post.data.tags?.includes(tag)) const filteredPosts = posts.filter((post: any) => post.data.tags?.includes(tag))
const getTagTitle = (tag: string, locale: string) => {
const titles = {
en: `Tag: ${tag}`,
zh: `标签:${tag}`,
}
return titles[locale as keyof typeof titles] || titles["en"]
}
--- ---
<HomeLayout> <HomeLayout>
<div class="container mx-auto px-4"> <div class="mb-4 mt-2 text-2xl">
<h1 class="mb-6 text-2xl font-bold"> {tag}
{getTagTitle(tag, locale)}
</h1>
{
filteredPosts.length > 0 ? (
<ul class="space-y-4">
{filteredPosts.map((post: any) => (
<li>
<a
href={
defaultLanguage === "zh"
? locale === "zh"
? `/posts/${post.id}/`
: `/en/posts/${post.id}/`
: locale === "en"
? `/posts/${post.id}/`
: `/zh/posts/${post.id}/`
}
class="block rounded-lg p-2 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800"
>
<div class="flex items-center text-lg">
<p class="mr-4 w-36 flex-shrink-0 truncate text-gray-500 dark:text-gray-400">
<time>{formatDate(post.data.pubDate)}</time>
</p>
<p class="line-clamp-2 flex-grow">{post.data.title}</p>
</div>
</a>
</li>
))}
</ul>
) : (
<p class="text-gray-500">
{locale === "en"
? `No posts found for tag "${tag}"`
: `没有找到标签为 "${tag}" 的文章`}
</p>
)
}
</div> </div>
{
filteredPosts.length > 0 ? (
<ul class="space-y-4">
{filteredPosts.map((post: any) => (
<li>
<a
href={`/${lang}/posts/${post.id}/`}
class="block rounded-lg p-2 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800"
>
<div class="flex items-center text-lg">
<p class="mr-4 w-36 flex-shrink-0 truncate text-gray-500 dark:text-gray-400">
<time>{formatDate(post.data.pubDate)}</time>
</p>
<p class="line-clamp-2 flex-grow">{post.data.title}</p>
</div>
</a>
</li>
))}
</ul>
) : (
<p class="text-gray-500">{t("tag.no_posts")}</p>
)
}
</HomeLayout> </HomeLayout>

View file

@ -1,32 +1,13 @@
import { Languages } from "lucide-react" import { Languages } from "lucide-react"
import { defaultLanguage } from "~/config"
interface LanguageToggleProps {
currentLocale: string
}
export function LanguageToggle({ currentLocale }: LanguageToggleProps) {
const getNewPath = (path: string): string => {
if (path === "/" || path === "") {
return defaultLanguage === "zh" ? "/en" : "/zh"
}
if (path === "/en" || path === "/zh") {
return "/"
}
return defaultLanguage === "zh"
? currentLocale === "zh"
? `/en${path}`
: path.replace("/en", "")
: currentLocale === "en"
? `/zh${path}`
: path.replace("/zh", "")
}
export function LanguageToggle() {
const handleLanguageToggle = () => { const handleLanguageToggle = () => {
const currentPath = window.location.pathname const currentPath = window.location.pathname
const newPath = getNewPath(currentPath)
const newPath = currentPath.includes("/en")
? currentPath.replace("/en", "/zh")
: currentPath.replace("/zh", "/en")
window.location.href = newPath window.location.href = newPath
} }

View file

@ -1,9 +1,3 @@
---
layout: src/layouts/post.astro
title: "关于我"
description: "前端开发者 | 摄影爱好者 | 骑行爱好者"
---
export const title = "About Me" export const title = "About Me"
## {title} ## {title}

8
src/config/en/intro.mdx Normal file
View file

@ -0,0 +1,8 @@
<div class="flex flex-col gap-2">
<p>
🖥️ Front-end developer (React)|📸 Photography enthusiast (Nikon)|🛸 Travel
explorer (experiencer)|🚴 Cycling freestyler (Java SILURO6-TOP 6)|🍎
Technology product enthusiast (Apple & Xiaomi)
</p>
<p>💬 Try, fail, retry. That's the rhythm of growth.</p>
</div>

View file

@ -1,6 +1,8 @@
import { Github, Twitter } from "lucide-react" import { Github, Twitter } from "lucide-react"
export const defaultLanguage: string = "zh" export const defaultLanguage: string = "en"
export const latestPosts = 8
const common = { const common = {
meta: { meta: {
@ -32,7 +34,6 @@ const common = {
}, },
], ],
}, },
latestPosts: 8,
} }
export const zh = { export const zh = {

View file

@ -1,9 +1,3 @@
---
layout: src/layouts/post.astro
title: "关于我"
description: "前端开发者 | 摄影爱好者 | 骑行爱好者"
---
export const title = "关于我" export const title = "关于我"
## {title} ## {title}

7
src/config/zh/intro.mdx Normal file
View file

@ -0,0 +1,7 @@
<div class="flex flex-col gap-2">
<p>
🖥️ 前端小学生|📸 摄影爱好者|🛸 旅行探索家|🚴 骑行蹭风选手|🍎
科技产品发烧友
</p>
<p>💬 路虽远行则将至,事虽难做则必成!</p>
</div>

View file

@ -1,7 +1,7 @@
import { glob } from "astro/loaders" import { glob } from "astro/loaders"
import { defineCollection, z } from "astro:content" import { defineCollection, z } from "astro:content"
const schema = z.object({ const postSchema = z.object({
title: z.string(), title: z.string(),
description: z.string(), description: z.string(),
pubDate: z.coerce.date(), pubDate: z.coerce.date(),
@ -10,14 +10,17 @@ const schema = z.object({
tags: z.array(z.string()).optional(), tags: z.array(z.string()).optional(),
}) })
const posts_zh = defineCollection({ const enPostsCollection = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "src/content/zh/posts" }), loader: glob({ pattern: "**/*.{md,mdx}", base: "src/content/posts/en" }),
schema, schema: postSchema,
}) })
const posts_en = defineCollection({ const zhPostsCollection = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "src/content/en/posts" }), loader: glob({ pattern: "**/*.{md,mdx}", base: "src/content/posts/zh" }),
schema, schema: postSchema,
}) })
export const collections = { zh: posts_zh, en: posts_en } export const collections = {
enPosts: enPostsCollection,
zhPosts: zhPostsCollection,
}

View file

@ -1 +0,0 @@
🖥️ Front-end developer (React)|📸 Photography enthusiast (Nikon)|🛸 Travel explorer (experiencer)|🚴 Cycling freestyler (Java SILURO6-TOP 6)|🍎 Technology product enthusiast (Apple & Xiaomi)

View file

@ -1,26 +1,26 @@
--- ---
title: "为Astro添加国际化" title: "为 Astro 添加国际化"
author: Astro Learner author: Astro Learner
description: "在您的Astro网站中实现i18n的指南" description: "在您的 Astro 网站中实现 i18n 的指南"
image: image:
url: "https://docs.astro.build/assets/full-logo-light.png" url: "https://docs.astro.build/assets/full-logo-light.png"
alt: "Astro标志" alt: "Astro 标志"
pubDate: 2024-08-19 pubDate: 2024-08-19
tags: ["astro", "i18n", "本地化", "网页开发"] tags: ["astro", "i18n", "本地化", "网页开发"]
--- ---
让您的网站能够接触到全球受众是很重要的。以下是如何在Astro中实现国际化的方法。 让您的网站能够接触到全球受众是很重要的。以下是如何在 Astro 中实现国际化的方法。
## 实施步骤 ## 实施步骤
1. **设置**安装必要的i18n包 1. **设置**:安装必要的 i18n
2. **内容结构**:为多种语言组织内容 2. **内容结构**:为多种语言组织内容
3. **语言切换**:添加语言切换功能 3. **语言切换**:添加语言切换功能
4. **URL管理**:处理特定语言的路由 4. **URL 管理**:处理特定语言的路由
## 最佳实践 ## 最佳实践
- 将翻译保存在单独的文件中 - 将翻译保存在单独的文件中
- 使用ISO语言代码 - 使用 ISO 语言代码
- 实现后备语言 - 实现后备语言
- 考虑从右到左的语言 - 考虑从右到左的语言

View file

@ -1 +0,0 @@
🖥️ 前端小学生|📸 摄影爱好者|🛸 旅行探索家|🚴 骑行蹭风选手|🍎 科技产品发烧友

31
src/i18n/ui.ts Normal file
View file

@ -0,0 +1,31 @@
export const languages = {
en: "English",
zh: "简体中文",
}
export const defaultLang = "en"
export const langs = ["en", "zh"]
export const ui = {
en: {
"nav.home": "Home",
"nav.archive": "Archive",
"nav.tags": "Tags",
"nav.about": "About",
"nav.twitter": "Twitter",
"blog.latest": "Latest Posts",
"tag.title": "Tag:",
"tag.no_posts": "No posts found for tag",
},
zh: {
"nav.home": "首页",
"nav.about": "关于",
"nav.blog": "推特",
"nav.archive": "归档",
"nav.tags": "标签",
"blog.latest": "最近文章",
"tag.title": "标签:",
"tag.no_posts": "没有找到标签为的文章",
},
} as const

14
src/i18n/utils.ts Normal file
View file

@ -0,0 +1,14 @@
import { defaultLang, ui } from "./ui"
export function getLangFromUrl(url: URL) {
const [, lang] = url.pathname.split("/")
if (lang in ui) return lang as keyof typeof ui
return defaultLang
}
export function useTranslations(lang: keyof typeof ui) {
return function t(key: keyof (typeof ui)[typeof defaultLang]) {
// @ts-ignore
return ui[lang][key] || ui[defaultLang][key]
}
}

View file

@ -1,15 +1,15 @@
--- ---
import "~/styles/globals.css" import "~/styles/globals.css"
import { en, zh } from "~/config"
import { getLocale } from "astro-i18n-aut"
import { NoiseBg } from "~/components/react/noise-bg" import { NoiseBg } from "~/components/react/noise-bg"
import { en, zh } from "~/config"
import { getLangFromUrl } from "~/i18n/utils"
const locale = getLocale(Astro.url) const lang = getLangFromUrl(Astro.url)
const config = locale === "zh" ? zh.meta : en.meta const config = lang === "zh" ? zh.meta : en.meta
--- ---
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang={lang}>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
@ -39,7 +39,7 @@ const config = locale === "zh" ? zh.meta : en.meta
</script> </script>
</head> </head>
<body class="dark:bg-[#121212] dark:text-white"> <body class="dark:bg-[#121212] dark:text-white">
<div class="flex min-h-screen w-full justify-center px-10 md:p-0"> <div class="flex min-h-screen w-full justify-center px-6 md:p-0">
<slot /> <slot />
<NoiseBg /> <NoiseBg />
</div> </div>

View file

@ -1,7 +1,7 @@
--- ---
import Header from "~/components/astro/header.astro" import Header from "~/components/astro/header.astro"
import Navigation from "~/components/astro/navigation.astro" import Navigation from "~/components/astro/nav.astro"
import BaseLayout from "./base.astro" import BaseLayout from "~/layouts/base.astro"
--- ---
<BaseLayout> <BaseLayout>

View file

@ -0,0 +1,20 @@
---
import AboutContentEn from "~/config/en/about.mdx"
import AboutContentZh from "~/config/zh/about.mdx"
import { getLangFromUrl } from "~/i18n/utils"
import HomeLayout from "~/layouts/home.astro"
import { getLanguagePaths } from "~/utils/langs"
export function getStaticPaths() {
return getLanguagePaths()
}
const lang = getLangFromUrl(Astro.url)
const AboutContent = lang === "zh" ? AboutContentZh : AboutContentEn
---
<HomeLayout>
<div class="prose dark:prose-invert">
<AboutContent />
</div>
</HomeLayout>

View file

@ -1,11 +1,16 @@
--- ---
import { getLocale } from "astro-i18n-aut" import { getLangFromUrl } from "~/i18n/utils"
import { defaultLanguage } from "~/config"
import HomeLayout from "~/layouts/home.astro" import HomeLayout from "~/layouts/home.astro"
import { formatDate, getPostsByLocale } from "~/utils" import { formatDate, getPostsByLocale } from "~/utils"
import { getLanguagePaths } from "~/utils/langs"
const locale = getLocale(Astro.url) export function getStaticPaths() {
const posts = await getPostsByLocale(locale) return getLanguagePaths()
}
const lang = getLangFromUrl(Astro.url)
const posts = await getPostsByLocale(lang)
const postsByYear = posts.reduce( const postsByYear = posts.reduce(
(acc: Record<string, any[]>, post: any) => { (acc: Record<string, any[]>, post: any) => {
@ -30,17 +35,7 @@ const years = Object.keys(postsByYear).sort((a, b) => Number(b) - Number(a))
<h2 class="mb-4 text-2xl font-bold">{year}</h2> <h2 class="mb-4 text-2xl font-bold">{year}</h2>
<div class="space-y-2"> <div class="space-y-2">
{postsByYear[year].map((post: any) => ( {postsByYear[year].map((post: any) => (
<a <a href={`/${lang}/posts/${post.id}/`}>
href={
defaultLanguage === "zh"
? locale === "zh"
? `/posts/${post.id}/`
: `/en/posts/${post.id}/`
: locale === "en"
? `/posts/${post.id}/`
: `/zh/posts/${post.id}/`
}
>
<div class="my-2 flex text-lg"> <div class="my-2 flex text-lg">
<p class="flex w-36 flex-shrink-0 truncate text-gray-500 dark:text-gray-400"> <p class="flex w-36 flex-shrink-0 truncate text-gray-500 dark:text-gray-400">
<time>{formatDate(post.data.pubDate)}</time> <time>{formatDate(post.data.pubDate)}</time>

View file

@ -0,0 +1,17 @@
---
import Blog from "~/components/astro/blog.astro"
import Footer from "~/components/astro/footer.astro"
import Intro from "~/components/astro/intro.astro"
import HomeLayout from "~/layouts/home.astro"
import { getLanguagePaths } from "~/utils/langs"
export function getStaticPaths() {
return getLanguagePaths()
}
---
<HomeLayout>
<Intro />
<Blog />
<Footer />
</HomeLayout>

View file

@ -0,0 +1,26 @@
---
import { langs } from "~/i18n/ui"
import PostLayout from "~/layouts/post.astro"
import { getPostsByLocale } from "~/utils"
export async function getStaticPaths() {
const allPaths = []
for (const lang of langs) {
const posts = await getPostsByLocale(lang)
const paths = posts.map((post: any) => ({
params: { lang, slug: post.id },
props: { post },
}))
allPaths.push(...paths)
}
return allPaths
}
const { post } = Astro.props
---
<PostLayout {...post.data}>
<div set:html={post.rendered.html} />
</PostLayout>

View file

@ -0,0 +1,33 @@
---
import TagComponent from "~/components/astro/tag.astro"
import { langs } from "~/i18n/ui"
import { getPostsByLocale } from "~/utils"
export interface Props {
posts: any
tag: string
}
export async function getStaticPaths() {
const paths = await Promise.all(
langs.map(async (lang) => {
const posts = await getPostsByLocale(lang)
const uniqueTags = [
...new Set(posts.flatMap((post: any) => post.data.tags || [])),
]
return uniqueTags.map((tag) => ({
params: { tag, lang },
props: {
posts,
tag,
},
}))
}),
)
return paths.flat()
}
---
<TagComponent {...Astro.props} />

View file

@ -0,0 +1,26 @@
---
import { getLangFromUrl } from "~/i18n/utils"
import HomeLayout from "~/layouts/home.astro"
import { getPostsByLocale } from "~/utils"
import { getLanguagePaths } from "~/utils/langs"
export function getStaticPaths() {
return getLanguagePaths()
}
const lang = getLangFromUrl(Astro.url)
const posts = await getPostsByLocale(lang)
const tags = [...new Set(posts.map((post: any) => post.data.tags).flat())]
---
<HomeLayout>
<div>
{
tags.map((tag) => (
<span class="mx-4">
<a href={`/${lang}/tags/${tag}`}>{tag}</a>
</span>
))
}
</div>
</HomeLayout>

View file

@ -1,10 +0,0 @@
---
import { getLocale } from "astro-i18n-aut"
import AboutEn from "~/content/en/about.mdx"
import AboutZh from "~/content/zh/about.mdx"
const locale = getLocale(Astro.url)
const AboutContent = locale === "zh" ? AboutZh : AboutEn
---
<AboutContent />

View file

@ -1,19 +0,0 @@
---
import PostLayout from "~/layouts/post.astro"
import { getPostsByLocale } from "~/utils"
export async function getStaticPaths() {
const posts = await getPostsByLocale("en")
return posts.map((post: any) => ({
params: { slug: post.id },
props: { post },
}))
}
const { post } = Astro.props
---
<PostLayout {...post.data}>
<div set:html={post.rendered.html} />
</PostLayout>

View file

@ -1,22 +0,0 @@
---
import TagComponent from "~/components/astro/tag.astro"
import { getPostsByLocale } from "~/utils"
export async function getStaticPaths() {
const posts = await getPostsByLocale("en")
const uniqueTags = [
...new Set(posts.flatMap((post: any) => post.data.tags || [])),
]
return uniqueTags.map((tag) => ({
params: { tag },
props: {
posts,
tag,
locale: "en",
},
}))
}
---
<TagComponent {...Astro.props} />

View file

@ -1,12 +1,5 @@
--- ---
import Blog from "~/components/astro/blog.astro" import { defaultLanguage } from "~/config"
import Footer from "~/components/astro/footer.astro"
import Intro from "~/components/astro/intro.astro"
import HomeLayout from "~/layouts/home.astro"
--- ---
<HomeLayout> <meta http-equiv="refresh" content={`0;url=/${defaultLanguage}/`} />
<Intro />
<Blog />
<Footer />
</HomeLayout>

View file

@ -1,20 +0,0 @@
---
import { defaultLanguage } from "~/config"
import PostLayout from "~/layouts/post.astro"
import { getPostsByLocale } from "~/utils"
export async function getStaticPaths() {
const posts = await getPostsByLocale(defaultLanguage)
return posts.map((post: any) => ({
params: { slug: post.id },
props: { post },
}))
}
const { post } = Astro.props
---
<PostLayout {...post.data}>
<div set:html={post.rendered.html} />
</PostLayout>

View file

@ -1,12 +0,0 @@
import type { APIRoute } from "astro"
const getRobotsTxt = (sitemapURL: URL) => `
User-agent: *
Allow: /
Sitemap: ${sitemapURL.href}
`
export const GET: APIRoute = ({ site }) => {
const sitemapURL = new URL("sitemap-index.xml", site)
return new Response(getRobotsTxt(sitemapURL))
}

View file

@ -1,23 +0,0 @@
---
import TagComponent from "~/components/astro/tag.astro"
import { defaultLanguage } from "~/config"
import { getPostsByLocale } from "~/utils"
export async function getStaticPaths() {
const posts = await getPostsByLocale(defaultLanguage)
const uniqueTags = [
...new Set(posts.flatMap((post: any) => post.data.tags || [])),
]
return uniqueTags.map((tag) => ({
params: { tag },
props: {
posts,
tag,
locale: defaultLanguage,
},
}))
}
---
<TagComponent {...Astro.props} />

View file

@ -1,34 +0,0 @@
---
import { getLocale } from "astro-i18n-aut"
import { defaultLanguage } from "~/config"
import HomeLayout from "~/layouts/home.astro"
import { getPostsByLocale } from "~/utils"
const locale = getLocale(Astro.url)
const posts = await getPostsByLocale(locale)
const tags = [...new Set(posts.map((post: any) => post.data.tags).flat())]
---
<HomeLayout>
<div>
{
tags.map((tag) => (
<span class="mx-4">
<a
href={
defaultLanguage === "zh"
? locale === "zh"
? `/tags/${tag}`
: `/en/tags/${tag}`
: locale === "en"
? `/tags/${tag}`
: `/zh/tags/${tag}`
}
>
{tag}
</a>
</span>
))
}
</div>
</HomeLayout>

View file

@ -1,19 +0,0 @@
---
import PostLayout from "~/layouts/post.astro"
import { getPostsByLocale } from "~/utils"
export async function getStaticPaths() {
const posts = await getPostsByLocale("zh")
return posts.map((post: any) => ({
params: { slug: post.id },
props: { post },
}))
}
const { post } = Astro.props
---
<PostLayout {...post.data}>
<div set:html={post.rendered.html} />
</PostLayout>

View file

@ -1,22 +0,0 @@
---
import TagComponent from "~/components/astro/tag.astro"
import { getPostsByLocale } from "~/utils"
export async function getStaticPaths() {
const posts = await getPostsByLocale("zh")
const uniqueTags = [
...new Set(posts.flatMap((post: any) => post.data.tags || [])),
]
return uniqueTags.map((tag) => ({
params: { tag },
props: {
posts,
tag,
locale: "zh",
},
}))
}
---
<TagComponent {...Astro.props} />

View file

@ -3,20 +3,19 @@ import { getCollection } from "astro:content"
export const formatDate = (date: Date | string | undefined): string => { export const formatDate = (date: Date | string | undefined): string => {
const validDate = date ? new Date(date) : new Date() const validDate = date ? new Date(date) : new Date()
if (isNaN(validDate.getTime())) {
return "wrong date"
}
const year = validDate.getFullYear() const year = validDate.getFullYear()
const month = String(validDate.getMonth() + 1).padStart(2, "0") const month = String(validDate.getMonth() + 1).padStart(2, "0")
const day = String(validDate.getDate()).padStart(2, "0") const day = String(validDate.getDate()).padStart(2, "0")
return `${year}-${month}-${day}` return `${year}-${month}-${day}`
} }
export const getPostsByLocale = async (locale: string) => { export const getPostsByLocale = async (locale: string) => {
const posts = const posts =
locale === "zh" ? await getCollection("zh") : await getCollection("en") locale === "en"
? await getCollection("enPosts")
: await getCollection("zhPosts")
return posts.sort( return posts.sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(), (a: any, b: any) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
) )
} }

7
src/utils/langs.ts Normal file
View file

@ -0,0 +1,7 @@
import { langs } from "~/i18n/ui"
export function getLanguagePaths() {
return langs.map((lang) => ({
params: { lang },
}))
}