Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: i18n #343

Merged
merged 14 commits into from
Feb 23, 2024
2 changes: 2 additions & 0 deletions components/a.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import Link from "next/link";
import config from "../lib/config.mjs";

const A = props => {
return (
<Link
{...props}
className="no-underline hover:underline text-black dark:text-white font-normal cursor-pointer"
target={props.self ? "_self" : "_blank"}
locale={config.defaultLocale}
/>
);
};
Expand Down
30 changes: 28 additions & 2 deletions components/blog.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,19 @@ import Disqus from "./disqus";
import Comments from "./comments";

export default withView(props => {
const { children, title, date, author, view, id, tags, pageURL, image } =
props;
const {
children,
title,
date,
author,
view,
id,
tags,
pageURL,
image,
i18n,
postLocale,
} = props;
const [replies, setReplies] = useState([]);
const [likes, setLikes] = useState([]);

Expand Down Expand Up @@ -75,6 +86,21 @@ export default withView(props => {
</Link>
</div>
<div className="flex-1" />
{i18n?.length > 1 &&
i18n
.filter(language => language !== postLocale)
.map(language => {
return (
<Link
className="px-2 no-underline"
key={language}
href={id}
locale={language}
>
<small>🌐 {language.toUpperCase()}</small>
</Link>
);
})}
<div className={`justify-end ${withImageColor}`} id="views">
{view > 0 && <small>{view} views</small>}
</div>
Expand Down
2 changes: 2 additions & 0 deletions components/tag.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Link from "next/link";
import config from "../lib/config.mjs";

export default function Tag(props) {
const highlight = props.highlight
Expand All @@ -8,6 +9,7 @@ export default function Tag(props) {
<Link
href={`/tag/${props.tag.toLowerCase().trim()}`}
className="no-underline"
locale={config.defaultLocale}
>
<span
className={`before:content-['#'] duration-100 transition rounded inline-block p-1 mx-1 text-sm font-mono ${highlight}`}
Expand Down
1 change: 1 addition & 0 deletions lib/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const config = {
authorEmail: "[email protected]",
baseURL: "https://lawrenceli.me",
feedPath: "/atom.xml",
defaultLocale: "zh",
feedItemsCount: 10,
siteDescription: "Blog",
activityPubUser: "lawrence",
Expand Down
2 changes: 1 addition & 1 deletion lib/feed.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const buildFeed = async () => {
const markdownData = getMdPostsData();
const postContents = await Promise.all(
markdownData.map(async item => {
return await getMdContentById(item.id, defaultMarkdownDirectory);
return await getMdContentById(item.fileName, defaultMarkdownDirectory);
}),
);
// only output the recent blog posts
Expand Down
36 changes: 34 additions & 2 deletions lib/ssg.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,36 @@
.filter(fileName => fileName.includes(".md"))
.map(mdPostName => {
const id = mdPostName.replace(/\.md$/, "");
const locale = id.includes(".") ? id.split(".")[1] : config.defaultLocale;
const idWithoutLocale = id.replace(`.${locale}`, "");
const fullPath = path.join(mdDirectory, mdPostName);
const mdContent = fs.readFileSync(fullPath, "utf8");
const matterResult = matter(mdContent);
return {
id,
id: idWithoutLocale,
fileName: id,
locale,
...matterResult.data,
content: matterResult.content,
};
});
return mdPostsData.sort(sortByDate).filter(filterVisibility);
};

const getI18nLanguagesByTitle = (i18nTitle, mdPostNames) => {
const languages = new Set();
mdPostNames.forEach(fileName => {
if (fileName.startsWith(i18nTitle)) {
if (fileName.split(".")[1] === "md") {
languages.add(config.defaultLocale);
} else {
languages.add(fileName.split(".")[1]);
}
}
});
return [...languages];
};

export const getMdContentById = (id, mdDirectory, withHTMLString = true) => {
// default using the `./posts` for markdown directory
if (!mdDirectory) {
Expand All @@ -59,12 +77,24 @@
const isBlog = mdDirectory === defaultMarkdownDirectory; // if the markdown is blog article
const mdPostNames = fs.readdirSync(mdDirectory);
const mdPostsData = mdPostNames
.filter(name => name === id + ".md")
.filter(fileName => {
if (fileName.includes(`.${config.defaultLocale}`)) {
return fileName === id + ".md";
}
return fileName === id.replace(`.${config.defaultLocale}`, "") + ".md";
})
.map(async mdPostName => {
const fullPath = path.join(mdDirectory, mdPostName);
const mdContent = fs.readFileSync(fullPath, "utf8");
const fileStat = fs.statSync(fullPath);
const matterResult = matter(mdContent);
const postLocale = id.includes(".")
Fixed Show fixed Hide fixed
? id.split(".")[1]
: config.defaultLocale;
const languages = getI18nLanguagesByTitle(
mdPostName.split(".")[0],
mdPostNames,
);
const htmlResult = await renderHTMLfromMarkdownString(
matterResult.content,
isBlog,
Expand All @@ -75,6 +105,8 @@
// modifiedTime: fileStat.mtime.toISOString(),
author: config.authorName, // dafult authorName
// content: matterResult.content, // original markdown string
i18n: languages,
postLocale,
htmlStringContent: withHTMLString ? htmlResult.value : "", // rendered html string
htmlAst: isBlog
? fromHtml(htmlResult.value, {
Expand Down
9 changes: 5 additions & 4 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ module.exports = {
// experimental: {
// appDir: true,
// },
// i18n: {
// locales: ["zh"],
// defaultLocale: "zh",
// },
i18n: {
locales: ["zh", "en"],
defaultLocale: "zh",
localeDetection: false,
},
transpilePackages: ["react-tweet"],
async headers() {
return [
Expand Down
11 changes: 8 additions & 3 deletions pages/blog/[id].js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ export default PathId;

export const getStaticProps = async context => {
const { id } = context.params;
const mdData = await getMdContentById(id, defaultMarkdownDirectory, false);
const mdData = await getMdContentById(
`${id}.${context.locale}`,
defaultMarkdownDirectory,
false,
);
return {
props: mdData,
};
Expand All @@ -50,11 +54,12 @@ export const getStaticPaths = async () => {
const mdPostsData = getMdPostsData(path.join(process.cwd(), "posts"));
const paths = mdPostsData.map(data => {
return {
params: data,
params: { id: data.id },
locale: data.locale,
};
});
// const paths = [
// { params: { id: "hi" } },
// { params: { id: "hi" }, locale: "zh" },
// ];
return {
paths,
Expand Down
1 change: 1 addition & 0 deletions pages/blog/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const blogProps = {
date: "2022-08-26",
someKey: "someValeInJSXProps",
tags: "example, test, jsx",
locale: "en",
visible: true,
};

Expand Down
2 changes: 2 additions & 0 deletions pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const Index = function index({ allPostsData }) {
<div className="my-4" key={post.id}>
<Link
href={`/blog/${post.id}`}
locale={post.locale}
className={`text-lg font-normal no-underline hover:text-zinc-500`}
>
<span>{post.title}</span>
Expand All @@ -50,6 +51,7 @@ export const getStaticProps = async () => {
id: post.id,
title: post.title,
date: post.date,
locale: post.locale,
}));
return {
props: {
Expand Down
3 changes: 2 additions & 1 deletion pages/tag/[tag].js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export default function TagPage(props) {
<span className="text-lg">
<Link
href={`/blog/${post.id}`}
className={`p-0 no-underline font-normal `}
className={`p-0 no-underline font-normal`}
locale={post.locale}
>
{post.title}
</Link>
Expand Down
15 changes: 4 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
79 changes: 79 additions & 0 deletions posts/app-defaults-2023.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
title: "我的默认应用"
date: "2023-12-03"
description: "My default apps in 2023. https://defaults.rknight.me/"
modified: "2023-12-17"
tags: app, life, 2023
---

最近看到很多博主在[App Defaults](https://defaults.rknight.me/)中分享了他们的默认应用程序。以下是我自己的:

- 📨 邮件客户端
- Apple 邮件
- [Skiff 邮件](https://go.lawrenceli.me/skiff) `[email protected]`。
- 📮 邮件服务器
- Outlook(中国大陆可用)
- [Skiff 邮件](https://go.lawrenceli.me/skiff)。 很遗憾该服务尚不支持 POP3/IMAP。
- 📝 注释
- Apple Notes(使用 [montaigne.io](https://montaigne.io),可以将 Apple Notes 成为一个静态网站)
- ✅ 待办事项
- Apple 提醒
- 📷 iPhone 照片拍摄
- Apple 相机
- 🟦 照片管理
- Apple 照片与 iCloud 同步
- 📆 日历
- 服务:iCloud 日历
- 客户端:Apple 日历
- 📁 云文件存储
- iCloud 云盘 (iCloud+)
- [阿里云盘](https://www.aliyundrive.com/)(适用于 Apple TV)
- 📖 RSS
- 客户端:iOS/macOS 上的 [Reeder](https://reederapp.com/)
- 服务端:自托管 [FreshRSS](https://freshrss.org/index.html),出色的 PWA 和 Web 推送。
- 🙍🏻‍♂️ 通讯录
- N/A,没有联系人。
- 🌐 浏览器
- Mac 上的 Chrome
- iOS 上的 Safari
- 💬 聊天
- [微信](https://www.wechat.com/)(中国必备应用)
- [Microsoft Teams](https://apps.apple.com/ph/app/microsoft-teams/id1113153706)(上班用)
- 🔖 书签
- [Instapaper](https://www.instapaper.com/)
- 📑 稍后阅读
- Reeder + Instapaper
- 📜 文字处理
- [ONLYOFFICE](https://www.onlyoffice.com/)(开源 Office,与微软完全兼容)
- [Obsidian](https://obsidian.md/)(流行的 Markdown 编辑器,可通过 iCloud 同步)
- 📈 电子表格
- ONLYOFFICE(开源 Office)
- 📊 演示
- [Slidev](https://sli.dev/)(通过 Markdown 生成幻灯片)
- 🛒 购物清单
- 在 Apple 提醒共享提醒列表
- 🍴膳食计划
- 无
- 💰 预算和个人理财
- 以前用过 [iOS 的 Finances 2](https://hochgatterer.me/finances/ios/),目前正在尝试 [BeanWise](https://apps.apple.com/us/app/beanwise/id6446314789?ref=https://lawrenceli.me)
- 📰 新闻
- [Solidot](https://solidot.org) (中文版 [slashdot](https://slashdot.org) )
- [路透社](https://www.reuters.com/)
- [Hacker News](https://news.ycombinator.com/)
- 🎵 音乐
- [Apple Music](https://music.apple.com/)(学生订阅只需每月 6 元)
- [网易云音乐](https://music.163.com/)(作为 Apple Music 的补充)
- 🎤 播客
- [Apple Podcast](https://www.apple.com/apple-podcasts/) 泛用型播客客户端,无审查
- 🔐密码管理
- iCloud KeyChain
- [Google 身份验证器](https://apps.apple.com/us/app/google-authenticator/id388497605) 多因素身份验证

## 额外默认设置

- 🚀 自托管
- [Cloudflare](https://lawrenceli.me/blog/cloudflare)(用于 CDN 和 DNS)、[Vercel](https://vercel.com)、[Cloudflare Workers](https://developers.cloudflare.com/workers/), [fly.io](https://fly.io)
- 🤖 自动化
- [IFTTT](https://ifttt.com),Apple 快捷指令(前 Workflow)
- 🛜 网络工具
- [Tailscale](https://tailscale.com/)、[Cloudflare WARP](https://1.1.1.1)、[MerlinClash](https://mcreadme.gitbook.io/mc/)
File renamed without changes.
File renamed without changes.
13 changes: 13 additions & 0 deletions posts/pre-rendering.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: "Two Forms of Pre-rendering"
date: "1970-01-01"
description: "SSG & SSR"
tags: ssg, ssr
---

Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.

- **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
- **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.

Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.
10 changes: 5 additions & 5 deletions posts/pre-rendering.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
title: "Two Forms of Pre-rendering"
title: "两种预渲染形式"
date: "1970-01-01"
description: "SSG & SSR"
tags: ssg, ssr
---

Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.
Next.js 有两种形式的预渲染:**静态生成**和**服务器端渲染**。 区别在于**何时**生成页面的 HTML

- **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
- **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.
- **静态生成**是在**构建时**生成 HTML 的预渲染方法。 然后,每个请求都会*重用*预渲染的 HTML。
- **服务器端渲染**是在**每个请求**上生成 HTML 的预渲染方法。

Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.
重要的是,Next.js 允许您**选择**每个页面使用哪种预渲染表单。 您可以通过对大多数页面使用静态生成并对其他页面使用服务器端渲染来创建“混合”Next.js 应用程序。
Loading