我是一个 HR,碰到疑似“外星人”,居然让我帮他修理星际飞船?!

我是一个 HR,碰到疑似“外星人 ”,居然让我帮他修理星际飞船?! 本来我想拒绝,但是他大手一挥,我飞出了三米远。更没想到的是,四千年前的金字塔居然也和他有关! 就在两天前,一个普通的工作日午后,我结束了一上午繁忙的人事工作,关上屏幕,准备好好睡个午觉。 眼前的世界逐渐昏暗,窗外时不时传来汽车行驶和空调的运转声音,我的大脑慢慢降低频率,进入了睡眠模式 。 奇怪,我的身体仿佛在下沉,还没来及细想,下沉的速度变得越来越快,伴随着刺眼的光芒和巨大的轰鸣声,还没来及反应过来,身体就被一股强大的力量拉了下去,我失去了知觉。 等再次醒来时,我还在办公室的工位上,但奇怪的是同事们都不见了。 一个身躯高大、外形丑陋、浑身好像包裹着金属石板的奇怪生物走了出来,它的眼睛闪烁着的蓝光。 我惊恐地向后退了两步,“你是?” “�* � ” “我听不懂” 难道碰到了外星人? “锟斤拷打锟锟” 怪物检索着语言,“呵呵 小虫虫,又和小虫虫见面了” “你是?” 我脑中有一百万个问题,但现在只能吐出这两个字。 “小虫虫,TL;DR ,我是迩迓,从隔壁星系团过来的,路上遇到点状况,跃迁机出了毛病…” “隔壁星系团?跃迁机?” 我满脸疑惑,只听懂他似乎叫二牙。 “害 小虫虫,就你们隔壁不远。我需要几位维修工程师修理我的跃迁机,也就是…交通工具,飞船。” “本来是不需要你们小虫虫的,但是我的同伴们都牺牲了,只能…” 二牙丑陋的面孔上划过一丝悲伤,看向了我。 “我…我是一个 HR ,不会维修飞船。” 我尽量让自己的声音听起来镇定。 “HR 不是会找人吗,赶紧找人!立刻马上!” 二牙把巨头猛一凑近,一股气流撞到我身上,我连连退了几米远。 “好好好!我…我马上找!马上找!” 我扶着墙一阵头晕目眩,盘算着去哪找会维修飞船的人。 “还在等什么!耽误我的大事,小虫虫会付出代价!” 二牙看我还呆在原地,不耐烦地咧着大嘴,又把头猛地一凑。 呯!办公室的桌子椅子连带着桌上的东西都飞了起来,然后重重砸在墙上。 我赶紧爬起来打开电脑,在招聘网站上搜索“维修工”,但几乎都是些水管、空调维修,一无所获。 突然,我瞥见之前群里有人分享的一个职位描述自动生成工具:职生机 职生机 Jobot - 职位描述生成机器, 专注智能招聘jobot.gegegugu.com 职生机职生机,能不能有有一丝生机只能靠你了! 我飞快输入“急招飞船修理人员,帮助外星人二牙回家”,点击生成按钮, 哗啦啦~职位描述内容几秒种就生成出来了!嘿嘿嘿,这下应该能侥幸捡回一条命了。 正欣喜时,我一回头看到站在身后的二牙。 ”这就是你们小虫虫的智能吗?“ 二牙显得有些惊讶。 ”是的,这是我们最新的智能工具,能大大提高招聘和工作效率“ ”呵呵,小虫虫“ 二牙仰头笑了起来,好像从地狱深渊传来了几丝干笑”,”你们的智能,就是用在这种琐碎的事情上吗?“ 我注视着他,无言以对。 “几千年前我来过这里,那时候你们这些小虫虫喜欢盖房子,几千年过去了,小虫虫的智能还是这么低级” 二牙不屑地看着我。 “几千年前…你来过这里?什么盖房子?” 我更加疑惑了。 “用石头盖房子,后来我看的着急,只好帮他们也盖了几个小房子,顺便,把他们也盖在里面了…”。 二牙说话间随手一指,空中出现一团光芒,接着几座金字塔的模型显现了出来。 “金字塔?金字塔是你建的???”我震惊地看向他。 “没错。” 二牙似乎在描述一件微不足道的事情。 叮~叮~叮~ 是求职者们发的招呼提醒,我赶紧回到电脑前查看。 几天后,我再次看到了二牙,他的身形看起来比上次更加高大,更有力量。 “谢谢你们,小虫虫。” 他的语气缓和了些。 “不客气,我们还会再见面吗?“ ”也许吧 哈哈,几千年前你们也问过我同样的问题…“ “是吗?” 我脑海里想象金字塔下的苦工们面对这个可怕的怪物,问出这个问题。 “哈哈哈哈,朝菌不知晦朔,蟪蛄不知春秋,可悲啊…“ 二牙长长地叹息了一声,扭头进入飞船,很快就消失在天空中。 飞船巨大的轰鸣声和刺眼的白光,让我醒了过来。 我抬头看到自己还在工位上,办公室一切如常,同事们都刚午睡醒来,伸着懒腰。 只是电脑屏幕上,还打开着 职生机的网页 jobot.gegegugu.com 上面清楚地写着:急招飞船修理人员,帮助外星人二牙回家

2024/5/24
articleCard.readMore

如何在 GitHub README 中插入视频?原来这么简单

当我们发布一款产品时,通常会制作一个演示视频来展示我们产品的功能和特性。如今,很多 GitHub 仓库也是如此。 但是,如何在GitHub README 中插入视频呢? 在此之前,我们尝试将视频文件添加到代码仓库中,但由于平台的限制,视频文件显示为URL。我们还尝试使用 Markdown 或 HTML 来嵌入视频,但根本不起作用。 好消息是,如今将本地视频插入到 GitHub README中 非常简单! 只需如下几步即可: 1. 在 GitHub 在线仓库中编辑 README 文件 编辑页面的链接类似: https://github.com/xumeng/ai-careers/edit/master/README.md 2. 把本地视频文件拖到编辑区域内 GitHub 会自动上传视频文件,然后展示一个资源文件的链接地址,类似: https://github.com/xumeng/ai-careers/assets/2187660/7362e4b8-6318-4cfc-af63-b8921455e434 3. 预览检查效果,然后提交文件更新,搞定! GitHub README 示例: https://github.com/xumeng/ai-careers 相关链接 https://stackoverflow.com/a/78521560/3090339 https://amonxu.medium.com/how-to-embed-a-video-into-github-readme-so-easy-c298ca92d537

2024/5/23
articleCard.readMore

How to embed a video into GitHub-README? So easy!

When we launch a product, we usually make a video to show our product’s functions and features. We also do this for our GitHub repository now. However, how can we embed a video into a GitHub README? In the past, we tried to add a video file to the repository, but the video file showed as a URL given the limitations of the platform. We also tried using markdown or HTML to embed a video, but it did not work at all. Good news: Embedding a local video into a GitHub README is very easy now! Just follow these steps: 1. Edit the README file on the GitHub online repository The edit page url likes https://github.com/xumeng/ai-careers/edit/master/README.md 2. Drag the local video file to the edit panel GitHub will uploading the file automatically, then it will showing a video asset url likes https://github.com/xumeng/ai-careers/assets/2187660/7362e4b8-6318-4cfc-af63-b8921455e434 3. Preview and commit the changes, We’re done! GitHub README Demo: https://github.com/xumeng/ai-careers References https://stackoverflow.com/a/78521560/3090339 https://amonxu.medium.com/how-to-embed-a-video-into-github-readme-so-easy-c298ca92d537

2024/5/23
articleCard.readMore

十年磨一剑,今朝更锃亮:把 Hexo blog + hexo-theme-next 博客升级到最新版本

磨剑 现在很有意思,凡事讲究个敏捷、迭代、快。稍微一个不留神,就会发现自己被时代远远甩在后面。 我的 Blog 托管在 GitHub Pages + Hexo,有段时间没更新 Blog 程序了。这两天心血来潮想试下更新,发现程序落后好几个大版本(当前使用 3.x,最新版本 7.x)。 今天头铁,备份 Blog 后果断上手开干,先报下总结: 好消息:升级过程意外的顺利 坏消息:升级后没感觉到明显变化 (可能是我期望太高了🤔️) 升级后的程序版本信息: 1 2 3 4 5 ❯ hexo version INFO Validating config hexo: 7.2.0 hexo-cli: 4.3.2 os: darwin 22.4.0 13.3.1 十年 Blog 最早大概是 2011、2012 年大学期间随便买了个域名,用 WordPress 搭建。https://amonxu.com/2012/05/21/zh-CN/2012-05-21-hello-world/ 2013 年接触了 GitHub Pages,觉得 WordPress 实在太不 Geek 了,马上把 Blog 迁移到 GitHub Pages + Jekyll。 https://amonxu.com/2013/04/18/zh-CN/2013-04-18-wordpress-to-github/ 2015 年时,看到 Hexo 的几个主题很有意思,瞬间又感觉 Jekyll 不够酷了,用 Hexo + Node.js 替代了 Jekyll + Ruby。https://amonxu.com/2013/05/23/zh-CN/2013-05-22-hello--my-blog/ 。又换了现在这个域名 https://amonxu.com 后面除了更新文章,换过几个主题,暂时没再继续折腾 Blog,直到今天。 折腾时看到很多程序停止维护,更新时间动不动就是 8, 9 years ago,感叹光阴易逝。 使用过的的Hexo主题列表 GitHub Pages 部署记录 升级参考资料: https://www.imczw.com/post/tech/hexo5-next8-updated.html

2024/5/14
articleCard.readMore

Simply way to support multiple languages i18n in Next.js 14 (Based on App Router)

Introduction: This post introduces the implementation of i18n internationalization multi-language feature in Next.js 14 (based on App Router), and takes into consideration actual scenarios to optimize and perfect the feature step-by-step. By reading this post, you will immediately get how to implement i18n in Next.js. Preface In an era where the Internet world is becoming increasingly flattening, the importance of multi-language products is growing. Fortunately, Next.js allows us to quickly support multi-language with simple configurations and code. However, when we search for how Next.js supports multi-languages on the Internet, we might find a variety of implementations, jumbled information, and excessively clever solutions. Then we become confused and start to question: What’s the issue here? Today, let’s implement multi-language from 0 to 1 in Next.js and unravel the mystery of multi-language. We can refer to the i18n introduction in the Next.js official documentation here: https://nextjs.org/docs/app/building-your-application/routing/internationalization, which is quite clear and detailed. This article will be based on this documentation. Before we begin, let’s take a look at the final running effect: https://next-i18n-demo-two.vercel.app/ Ready to work First, We create a Next.js app, 1 npx create-next-app@latest Plese select App Router, I am using TypeScript here. 1 2 3 4 5 6 7 8 ❯ npx create-next-app@latest ✔ What is your project named? … `next-i18n-demo` ✔ Would you like to use TypeScript? … No / `Yes` ✔ Would you like to use ESLint? … No / `Yes` ✔ Would you like to use Tailwind CSS? … No / `Yes ✔ Would you like to use `src/` directory? … No / `Yes` ✔ Would you like to use App Router? (recommended) … No / `Yes` ✔ Would you like to customize the default import alias (@/*)? … `No` / Yes Run locally, 1 npm run dev Open http://localhost:3000 and you will see that it is running okay. Internationalization introduction Before we start it, let’s briefly introduce about internationalization. internationalization, aka i18n, this means supporting multiple languages, cultures, and customs in products, mainly including language, time, currency symbols, etc. This article will focus only on the language part. In terms of internationalization, a common approach is for a website to default to a certain language’s official site (usually English), and to support the selection of language or region, allowing for a switch to different language versions of the site. Specifically, some websites use a language abbreviation as a prefix, such as en.wikipedia.org, zh.wikipedia.org; some use it as a path suffix, such as aws.amazon.com/cn, aws.amazon.com/jp, and others distinguish based on the country or region domain name, such as apple.cn, apple.jp. Among these, en, zh, cn, jp, etc., are language codes, which can vary slightly in different versions. You can refer to the reference materials at the end of the article for specifics. In this article’s particular case, the ISO_3166 codes en and zh will be used to represent English and Chinese respectively. Begin to configure Multi-Languages The original file structure of the project was: 1 2 3 4 5 6 7 8 9 10 11 12 ├── package.json ├── public │   ├── next.svg │   └── vercel.svg ├── src │   └── app │   ├── favicon.ico │   ├── globals.css │   ├── layout.tsx │   └── page.tsx ├── tailwind.config.ts └── tsconfig.json We create a new folder named [lang] in the app directory, then move laytout.tsx and page.tsx from the app directory to [locales]. The file structure after moving is: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ├── package.json ├── postcss.config.mjs ├── public │   ├── next.svg │   └── vercel.svg ├── src │   └── app │   ├── [lang] │   │   ├── layout.tsx │   │   └── page.tsx │   ├── favicon.ico │   └── globals.css ├── tailwind.config.ts └── tsconfig.json Tips: Please modify the reference position of globals.css in layout.tsx. Next, we define json resource files for different languages, which you can put in your preferred file directory. I put it in public/dictionaries. The file format is as follows: en.json 1 2 3 4 5 6 7 8 9 10 { "page": { "title": "Next.js i18n Demo", "desc": "How to implement i18n with Next.js (based on App Router)" }, "home": { "title": "Hello, Next.js i18n", "desc": "This is a demo of Next.js i18n" } } zh.json 1 2 3 4 5 6 7 8 9 10 { "page": { "title": "Next.js i18n 示例", "desc": "搞懂 Next.js 实现 i18n 国际化多语言(基于App Router)" }, "home": { "title": "你好, Next.js i18n", "desc": "这是一个 Next.js i18n 示例" } } Then, we create a file to load the multi-language resource files and get the corresponding language text. Add dictionaries.js in the app/[lang] directory. Make sure the file directory and file name are correct and match. 1 2 3 4 5 6 7 8 import 'server-only' const dictionaries = { en: () => import('./dictionaries/en.json').then((module) => module.default), zh: () => import('./dictionaries/zh.json').then((module) => module.default), } export const getDictionary = async (locale) => dictionaries[locale]() Using Multi-Languages We use the multi-language feature on the pages.tsx page. First, add the lang parameter for the function, add async for the function, 1 2 3 export default async function Home({ params: { lang } }: { params: { lang: string } }) { ... } Use it on the page, add the multi-language fuction call, 1 const t = await getDictionary(lang); For convenience, I clean up the default code on page.tsx and only retain the text display. 1 2 3 4 5 6 <main className="flex min-h-screen flex-col items-center justify-between p-24"> <p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30"> {t.home.title} </p> {t.home.desc} </main> Restart the program or wait for the program to hot update successfully, open different language pages http://localhost:3000/en http://localhost:3000/zh to check the effect. Setting the Default Language It looks pretty good, but careful friends will find that opening http://localhost:3000 will result in a 404 error. To solve this problem, we need to set a default language when no language is selected. For this, we can create a middleware.ts in the src directory, and then copy the code from the documentation. The core logic is simple: Check whether there is a certain language identifier in URL’s pathname. If so, return directly. Otherwise, get the appropriate language and redirect the URL to /${locale}${pathname} The focus is on the getLocale function. We need to specify the suitable language. For now, let’s deal with this simply: use the default defaultLocale = "en" . 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import { NextRequest, NextResponse } from "next/server"; let locales = ["en", "zh"]; let defaultLocale = "en"; // Get the preferred locale, similar to the above or using a library function getLocale(request: NextRequest) { return defaultLocale; } export function middleware(request: NextRequest) { // Check if there is any supported locale in the pathname const { pathname } = request.nextUrl; const pathnameHasLocale = locales.some( (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}` ); if (pathnameHasLocale) return; // Redirect if there is no locale const locale = getLocale(request); request.nextUrl.pathname = `/${locale}${pathname}`; // e.g. incoming request is /products // The new URL is now /en-US/products return NextResponse.redirect(request.nextUrl); } export const config = { matcher: [ // Skip all internal paths (_next) "/((?!_next).*)", // Optional: only run on root (/) URL // '/' ], }; After the program updates, we open http://localhost:3000/ and see that it will automatically redirect to the default language page. Optimization of Getting the Default Language In the previous step, while get the default language, we treated it simply as defaultLocale = "en". A more graceful way is: Set the default language based on the user’s system or browser language. We can achieve this by getting the Accept-Language field from the browser’s HTTP headers. The data format is approximately as follows: 1 2 3 4 English: accept-language: en-US,en;q=0.5 Chinese: accept-language: zh-CN,zh-Hans;q=0.9 We update middleware as follows: Get the Accept-Language from the HTTP headers. If it’s empty, then return the default language. Parse the language list in Accept-Language and match to get the corresponding language based on the configured language list. (If there is no match, return the default language) Install dependencies @formatjs/intl-localematcher, negotiator, @types/negotiator, and implement the following logic: 1 2 3 4 5 6 7 function getLocale(request: NextRequest) { const acceptLang = request.headers.get("Accept-Language"); if (!acceptLang) return defaultLocale; const headers = { "accept-language": acceptLang }; const languages = new Negotiator({ headers }).languages(); return match(languages, locales, defaultLocale); } By changing the system language, open http://localhost:3000 and it will automatically redirect to the page with the same system language. Test successfully. Other Handling of Multi-Language Storing the Language Going a step further, we can store the user’s web page language in the cookies and use it on the next visit: 1 2 3 4 5 6 7 // Get Cookie if (request.cookies.has(cookieName)) { return request.cookies.get(cookieName)!.value; } // Set Cookie response.cookies.set(cookieName, locale); Web Metadata(Page Title/Descriptions..) When using i18n in web page metadata, add the following code to page.tsx: 1 2 3 4 5 6 7 export async function generateMetadata({ params: { lang } } : { params: { lang: string } }) { const t = await getDictionary(lang); return { title: t.page.title, description: t.page.desc, }; } SSG(Static Generation) When handling i18n in SSG, the code in layout.tsx is as follows: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 interface LangParams { lang: string; } export async function generateStaticParams() { return [{ lang: "en" }, { lang: "zh" }]; } export default function RootLayout({ children, params, }: Readonly<{ children: React.ReactNode; params: LangParams; }>) { return ( <html lang={params.lang}> <body className={inter.className}>{children}</body> </html> ); } Language Switch(Language Switcher or Links) You can add a language swicher (like a drop-down menu) or some links. For example, 1 2 3 4 5 <div className="space-x-2"> <Link href="/en">English</Link> <span>|</span> <Link href="/zh">Chinese</Link> </div> End Through the learning of the above steps, we initially familiarize and practice using multi-language in Next.js. A journey of thousand miles begins with a single step. The work of i18n is not limited to these, and of course, other areas need improvement which I’d leave to you, the reader. Finally, here is the complete code of middleware.ts : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import Negotiator from "negotiator"; import { match } from "@formatjs/intl-localematcher"; import { NextRequest, NextResponse } from "next/server"; const locales = ["en", "zh"]; const defaultLocale = "zh"; const cookieName = "i18nlang"; // Get the preferred locale, similar to the above or using a library function getLocale(request: NextRequest): string { // Get locale from cookie if (request.cookies.has(cookieName)) return request.cookies.get(cookieName)!.value; // Get accept language from HTTP headers const acceptLang = request.headers.get("Accept-Language"); if (!acceptLang) return defaultLocale; // Get match locale const headers = { "accept-language": acceptLang }; const languages = new Negotiator({ headers }).languages(); return match(languages, locales, defaultLocale); } export function middleware(request: NextRequest) { if (request.nextUrl.pathname.startsWith("/_next")) return NextResponse.next(); // Check if there is any supported locale in the pathname const { pathname } = request.nextUrl; const pathnameHasLocale = locales.some( (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}` ); if (pathnameHasLocale) return; // Redirect if there is no locale const locale = getLocale(request); request.nextUrl.pathname = `/${locale}${pathname}`; // e.g. incoming request is /products // The new URL is now /en-US/products const response = NextResponse.redirect(request.nextUrl); // Set locale to cookie response.cookies.set(cookieName, locale); return response; } export const config = { matcher: [ // Skip all internal paths (_next) "/((?!_next).*)", // Optional: only run on root (/) URL // '/' ], }; You can get the full code from https://github.com/xumeng/next-i18n-demo . The finally running demo: https://next-i18n-demo-two.vercel.app/ Reference: https://nextjs.org/docs/app/building-your-application/routing/internationalization https://en.wikipedia.org/wiki/ISO_3166 https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes https://en.wikipedia.org/wiki/IETF_language_tag https://www.alchemysoftware.com/livedocs/ezscript/Topics/Catalyst/Language.htm

2024/4/10
articleCard.readMore

一篇文章学会 Next.js 实现 i18n 国际化多语言(基于App Router)

文章导读: 本文介绍了在 Next.js 14 (基于App Router) 中实现 i18n 国际化多语言功能,并考虑在真实的场景中,一步步优化将功能完善。通过阅读完本文,你将立即掌握如何在 Next.js 中实现 i18n。 前言 在互联网世界越来越扁平化的时代,产品的多语言显得越来越重要。幸运的在 Next.js 中通过简单的配置和代码即可快速支持多语言。但是,当我们在互联网上搜索 Next.js 如何支持多语言时,可能会看到各种实现方式、鱼龙混杂和奇技淫巧的方案,于是我们一头雾水,不禁怀疑人生:到底哪里出了问题? 今天,让我们从 0 到 1 在 Next.js 中实现一个多语言,揭开多语言的神秘面纱。 我们查看 Next.js 官方文档中的 i18n 介绍, https://nextjs.org/docs/app/building-your-application/routing/internationalization,比较清晰详细了,本文也将基于此篇文档制作。 开始之前,先看看最终运行效果:https://next-i18n-demo-two.vercel.app/ 准备工作 首先,我们初始化一个 Next.js app, 1 npx create-next-app@latest 请注意选择 App Router,此处我使用的是 TypeScript。 1 2 3 4 5 6 7 8 ❯ npx create-next-app@latest ✔ What is your project named? … `next-i18n-demo` ✔ Would you like to use TypeScript? … No / `Yes` ✔ Would you like to use ESLint? … No / `Yes` ✔ Would you like to use Tailwind CSS? … No / `Yes ✔ Would you like to use `src/` directory? … No / `Yes` ✔ Would you like to use App Router? (recommended) … No / `Yes` ✔ Would you like to customize the default import alias (@/*)? … `No` / Yes 本地启动, 1 npm run dev 打开 http://localhost:3000 看到程序运行正常。 国际化介绍 在正式开始之前,我们先简单介绍一下国际化,国际化 internationalization,简称 i18n,也即在产品中支持多国语言文化和环境风俗,主要包括语言/时间/货币符号等。这篇文章中将只专注于语言部分。 在国际化的具体呈现上,常见的方式是网站默认进入某个语言的官网(通常是英文),并支持选择语言或地区,进行切换网站的不同语言版本。 具体实现方式上,有的网站以语言简称为前缀,如 en.wikipedia.org, zh.wikipedia.org;有的网站以语言简称作为路径后缀,如 aws.amazon.com/cn, aws.amazon.com/jp,也有以国家地区域名为区分的,如以前的 apple.cn, apple.jp。 其中诸如 en, zh, cn, jp ,也即语言编码,在不同版本的语言编码版本中略有不同,具体可参考文章下方参考资料。 在本文案例中,将以 ISO_3166 中的 en 和 zh 编码分别代表英文和中文。 开始配置多语言 项目之前的文件结构: 1 2 3 4 5 6 7 8 9 10 11 12 ├── package.json ├── public │   ├── next.svg │   └── vercel.svg ├── src │   └── app │   ├── favicon.ico │   ├── globals.css │   ├── layout.tsx │   └── page.tsx ├── tailwind.config.ts └── tsconfig.json 我们在 app 目录新建一个文件夹 [lang],然后将 app 目录的 laytout.tsx 和 page.tsx 移入 [locales]中, 移动后的文件结构如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ├── package.json ├── postcss.config.mjs ├── public │   ├── next.svg │   └── vercel.svg ├── src │   └── app │   ├── [lang] │   │   ├── layout.tsx │   │   └── page.tsx │   ├── favicon.ico │   └── globals.css ├── tailwind.config.ts └── tsconfig.json Tips: 注意同步修改 layout.tsx 中 globals.css 的引用位置。 接下来,我们定义不同语言的 json 资源文件,你可以放入你习惯的文件目录,我这里放入 public/dictionaries,格式如下: en.json 1 2 3 4 5 6 7 8 9 10 { "page": { "title": "Next.js i18n Demo", "desc": "How to implement i18n with Next.js (based on App Router)" }, "home": { "title": "Hello, Next.js i18n", "desc": "This is a demo of Next.js i18n" } } zh.json 1 2 3 4 5 6 7 8 9 10 { "page": { "title": "Next.js i18n 示例", "desc": "搞懂 Next.js 实现 i18n 国际化多语言(基于App Router)" }, "home": { "title": "你好, Next.js i18n", "desc": "这是一个 Next.js i18n 示例" } } 紧接着,我们创建一个文件,用于加载多语言资源文件并获取相应语言文本。 在 app/[lang] 目录添加 dictionaries.js,注意检查文件目录及文件名是正确并匹配的。 1 2 3 4 5 6 7 8 import 'server-only' const dictionaries = { en: () => import('./dictionaries/en.json').then((module) => module.default), zh: () => import('./dictionaries/zh.json').then((module) => module.default), } export const getDictionary = async (locale) => dictionaries[locale]() 使用多语言 我们在 pages.tsx 页面中使用多语言功能。 首先,为函数增加 lang 参数,注意为函数添加 async 关键字, 1 2 3 export default async function Home({ params: { lang } }: { params: { lang: string } }) { ... } 添加多语言的调用, 1 const t = await getDictionary(lang); 在页面上使用,为了方便我将 page.tsx 上默认的代码进行清理,只保留文本展示。 1 2 3 4 5 6 <main className="flex min-h-screen flex-col items-center justify-between p-24"> <p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30"> {t.home.title} </p> {t.home.desc} </main> 重启程序或等程序热更新成功,分别打开不同语言的页面 http://localhost:3000/en http://localhost:3000/zh 即可看到效果。 设置默认语言 看起来不错,但是细心的朋友会发现打开 http://localhost:3000 会出现 404 error。为了解决这个问题,我们需要在未选择语言时,默认设置一个语言。 为此,我们可以在 src 目录创建一个 middleware.ts ,然后复制文档中的代码。 核心逻辑很简单: 判断 URL 的 pathname 中是否含有某个语言标识,如果有则直接返回,否则在获取合适的语言后,将 URL 重定向为 /${locale}${pathname} 重点在 getLocale 函数中,我们需要指定合适的语言。在此处,我们先简单处理:使用默认的 defaultLocale = "en" 。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import { NextRequest, NextResponse } from "next/server"; let locales = ["en", "zh"]; let defaultLocale = "en"; // Get the preferred locale, similar to the above or using a library function getLocale(request: NextRequest) { return defaultLocale; } export function middleware(request: NextRequest) { // Check if there is any supported locale in the pathname const { pathname } = request.nextUrl; const pathnameHasLocale = locales.some( (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}` ); if (pathnameHasLocale) return; // Redirect if there is no locale const locale = getLocale(request); request.nextUrl.pathname = `/${locale}${pathname}`; // e.g. incoming request is /products // The new URL is now /en-US/products return NextResponse.redirect(request.nextUrl); } export const config = { matcher: [ // Skip all internal paths (_next) "/((?!_next).*)", // Optional: only run on root (/) URL // '/' ], }; 程序更新后,我们打开 http://localhost:3000/ 可以看到会自动跳转到设置的默认语言页面。 获取默认语言的优化 在上一节获取默认语言时,我们简单处理为 defaultLocale = "en" ,更优雅的方式是:根据用户的系统或者浏览器的语言来设置默认语言: 我们可以通过获取浏览器 HTTP headers 中的 Accept-Language 字段来达到目的。它的数据格式大致如下: 1 2 3 4 英文时: accept-language: en-US,en;q=0.5 中文时: accept-language: zh-CN,zh-Hans;q=0.9 我们将 middleware 改造如下: 从 HTTP headers 中获取 Accept-Language,如果为空则返回默认语言 解析 Accept-Language 中的语言列表,并根据配置的语言列表,匹配获取对应的语言(如果没有则返回默认语言) 安装依赖 @formatjs/intl-localematcher, negotiator, @types/negotiator,并实现如下逻辑: 1 2 3 4 5 6 7 function getLocale(request: NextRequest) { const acceptLang = request.headers.get("Accept-Language"); if (!acceptLang) return defaultLocale; const headers = { "accept-language": acceptLang }; const languages = new Negotiator({ headers }).languages(); return match(languages, locales, defaultLocale); } 通过修改系统的语言,打开 http://localhost:3000 会自动跳转到同系统语言一致的页面,测试成功。 多语言的其它处理 存储用户网页语言 更进一步地,我们可以在 Cookie 中存储用户网页中的语言,并在下次访问时使用: 1 2 3 4 5 6 7 // 获取Cookie if (request.cookies.has(cookieName)) { return request.cookies.get(cookieName)!.value; } // 设置 Cookie response.cookies.set(cookieName, locale); 网页标题描述等的多语言处理 在网页 metadata 中使用多语言时,page.tsx添加如下代码: 1 2 3 4 5 6 7 export async function generateMetadata({ params: { lang } } : { params: { lang: string } }) { const t = await getDictionary(lang); return { title: t.page.title, description: t.page.desc, }; } SSG 的多语言处理 在处理静态站点(SSG)中使用多语言时,layout.tsx代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 interface LangParams { lang: string; } export async function generateStaticParams() { return [{ lang: "en" }, { lang: "zh" }]; } export default function RootLayout({ children, params, }: Readonly<{ children: React.ReactNode; params: LangParams; }>) { return ( <html lang={params.lang}> <body className={inter.className}>{children}</body> </html> ); } 切换多语言(语言选择器或链接) 可根据实际情况添加语言选择器(下拉框)或不同的链接,从而跳转到对应语言的页面。 例如通过链接实现多语言切换: 1 2 3 4 5 <div className="space-x-2"> <Link href="/en">English</Link> <span>|</span> <Link href="/zh">Chinese</Link> </div> 尾声 通过上述步骤的学习,我们初步熟悉并实践了在 Next.js 中使用多语言。千里之行,始于足下,国际化的工作不止于此,我们当然也还有尚未完善的地方,就留给屏幕前的你吧。 最后附上 middleware.ts 完整代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import Negotiator from "negotiator"; import { match } from "@formatjs/intl-localematcher"; import { NextRequest, NextResponse } from "next/server"; const locales = ["en", "zh"]; const defaultLocale = "zh"; const cookieName = "i18nlang"; // Get the preferred locale, similar to the above or using a library function getLocale(request: NextRequest): string { // Get locale from cookie if (request.cookies.has(cookieName)) return request.cookies.get(cookieName)!.value; // Get accept language from HTTP headers const acceptLang = request.headers.get("Accept-Language"); if (!acceptLang) return defaultLocale; // Get match locale const headers = { "accept-language": acceptLang }; const languages = new Negotiator({ headers }).languages(); return match(languages, locales, defaultLocale); } export function middleware(request: NextRequest) { if (request.nextUrl.pathname.startsWith("/_next")) return NextResponse.next(); // Check if there is any supported locale in the pathname const { pathname } = request.nextUrl; const pathnameHasLocale = locales.some( (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}` ); if (pathnameHasLocale) return; // Redirect if there is no locale const locale = getLocale(request); request.nextUrl.pathname = `/${locale}${pathname}`; // e.g. incoming request is /products // The new URL is now /en-US/products const response = NextResponse.redirect(request.nextUrl); // Set locale to cookie response.cookies.set(cookieName, locale); return response; } export const config = { matcher: [ // Skip all internal paths (_next) "/((?!_next).*)", // Optional: only run on root (/) URL // '/' ], }; 完整代码可在 https://github.com/xumeng/next-i18n-demo 获取。 最终运行效果:https://next-i18n-demo-two.vercel.app/ 参考资料: https://nextjs.org/docs/app/building-your-application/routing/internationalization https://en.wikipedia.org/wiki/ISO_3166 https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes https://en.wikipedia.org/wiki/IETF_language_tag https://www.alchemysoftware.com/livedocs/ezscript/Topics/Catalyst/Language.htm

2024/4/10
articleCard.readMore

《游子》

拖着行李箱的人们, 满载着一年的劳累, 穿过长长的铁道, 回到母亲怀内, 妈妈 我想再睡会 2024.2.7 腊月二十八 晨

2024/2/7
articleCard.readMore

How to solve error on CentOS "/lib64/libstdc++.so.6 version GLIBCXX_3.4.xx not found"

My personal server uses CentOS 7.9, and there are often strange errors when deploying some AI applications. For example, recently, an error was reported when deploying an application: 1 /lib64/libstdc++.so.6: version GLIBCXX_3.4.xx not found Online search for solutions, there are different opinions, either reinstall gcc, or recompile and install libstdc++,export LD_LIBRARY_PATH and so on. Several attempts have failed, and there is no way to upgrade the os version. Finally, a suitable solution is found, and it’s worked for me. Find and display all packages that provide libstdc++.so.6 as a library file through yum 1 sudo yum provides libstdc++.so.6 Download new version libstdc.so. NOTICE: Since I need version 3.4.22+, so I can just update it to 3.4.26. Other versions are the same. 1 2 3 cd /usr/local/lib64 sudo wget http://www.vuln.cn/wp-content/uploads/2019/08/libstdc.so_.6.0.26.zip unzip libstdc.so_.6.0.26.zip Copy libstdc++.so.6.0.26 to /usr/lib64 1 2 cp libstdc++.so.6.0.26 /usr/lib64 cd /usr/lib64 Check the soft link version of libstdc++.so.6, 1 ls -l | grep libstdc++ It may shows like this: 1 libstdc++.so.6 ->libstdc++.so.6.0.19 Remove /usr/lib64 original link libstdc++.so.6, you can backup it before remove. 1 sudo rm libstdc++.so.6 then, relink it. 1 sudo ln -s libstdc++.so.6.0.26 libstdc++.so.6 OK, check the newest link 1 strings /usr/lib64/libstdc++.so.6 | grep GLIBCXX It may shows like this: 1 2 3 4 5 GLIBCXX_3.4 ... GLIBCXX_3.4.25 GLIBCXX_3.4.26 GLIBCXX_DEBUG_MESSAGE_LENGTH Well Done!

2023/10/11
articleCard.readMore

Hexo blog title include special symbols reports error

In my Hexo blog, when I include special symbols inside the title in posts, it reports this error: 1 2 3 ERROR Process failed: _posts/en/XXX.md YAMLException: incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line at line 1, column 54: ... for Backend System Refactoring\: How to do backend system refac ... Solution: Include the title in single quotes, like: 1 2 3 title: 'How to read a book' lang: en date: 2023-01-01

2023/9/18
articleCard.readMore

Best Practices for Backend System Refactoring: How to do backend system refactoring efficiently and with high quality

Introduction As the company’s business experiences explosive growth, both the scale of requirements and the user base are rapidly expanding. This presents challenges to the system in terms of the three high (high performance, high concurrency, high availability), scalability, and maintainability. The old system, due to various limitations in its early design (such as the expertise of early participants, the foresight of architectural design, impatience of management, etc.), gradually becomes inadequate to meet current and future demands, exposing various issues. Developers find themselves dragging an old, worn-out car on the highway, which is a daunting task. In simpler terms, the codebase of the old system has become too problematic to fix, leading to a situation where developers either get buried in its issues or abandon the project altogether. At this point, a common question arises: should we continue trying to patch the issues, or should we choose to refactor? Patching is simply not feasible, not in this lifetime. Refactoring, on the other hand, requires the courage of a true hero because it’s a complex and time-consuming task. Moreover, it can impact ongoing business development or even bring it to a standstill. Often, product managers and executives are not supportive because they only care about one thing: when will the next feature be ready? Everything else is your development team’s problem. If you choose the path of refactoring, you must be prepared to see it through, no matter what. How can you ensure a successful refactoring from the get-go? Based on common practices in internet projects and my personal experience in refactoring projects, here is an outline of the common steps for refactoring systems of various sizes: Step 0: Convincing Stakeholders Refactoring is not just the responsibility of the development team; it’s a collective effort involving the entire project team. Refactoring can improve the system’s performance, availability, and scalability, as well as optimize and streamline business processes to meet new demands. It requires a significant investment of resources and must have the support of stakeholders. Typically, this requires explaining the benefits and drawbacks of refactoring, as well as the critical issues that would arise if refactoring is not done. Once you have their support, the refactoring work can officially begin. Participants: Technical Leader Step 1: Establish Clear Refactoring Goals Refactoring is a long-term endeavor; it’s not something that can be completed in one or two iterations, or even within a few months. It requires a substantial investment of manpower, resources, time, and effort. So, what are our goals in this prolonged battle? Are we aiming to meet the system’s high-performance requirements through a more efficient architecture? Or do we want to enhance code quality through refactoring? Perhaps we aim to introduce new technologies and frameworks to upgrade the entire system or optimize business processes to address previously unmet requirements. Once you have clear goals, you can work purposefully. Participants: Technical Leader, Architect Step 2: Define the Scope of Refactoring and Make Predictions Refactoring typically falls into several levels: Platform-level refactoring: Refactoring the entire platform, such as Alibaba transitioning from the LAMP stack to the Java platform. System-level refactoring: Refactoring specific business systems, such as introducing microservices or SOA architecture to break down monolithic applications. Architecture-level refactoring: Improving the existing architecture through adjustments and redesign, addressing architectural shortcomings, like decoupling business logic through layered design or introducing caching for improved concurrency. Business-level refactoring: Addressing specific business requirements that cannot be met due to the limitations of the current system, often involving the refactoring of business processes or database structures. Module/code-level refactoring: The most common form of refactoring, typically involving the use of design patterns, encapsulation, and code optimization to improve code structure and performance. Determine the level of refactoring required, the overall scope, and the technology stack for refactoring. Then, conduct a scientific assessment and estimation of the refactoring work. This includes identifying the costs, required resources, and time commitments, as well as assessing whether ongoing business requirements can be accommodated during the refactoring process. Once these predictions are established, you can provide stakeholders with a clear understanding, especially when they ask when new requirements can be delivered. Participants: Technical Leader, Architect, Developers Step 3: Familiarize Yourself with the Old System and Document Business Processes Refactoring is not about abandoning the old system; it’s about continuously working with it. Knowing your enemy is the key to victory. Refactoring not only requires a clear understanding of the new system’s goals and future, but also a deep familiarity with the old system, especially its pitfalls. At this stage, the participants in the refactoring project, especially those who worked on the old system, should document and organize information related to the old system’s business and technical details. This includes collecting documents such as design documents, technical documents, architecture diagrams, UML diagrams, and ER diagrams related to the system. The following are common preparation tasks before refactoring the old system: Gathering information and documentation related to the old system, including design documents, technical documents, architectural diagrams, UML diagrams, ER diagrams, and other graphical materials. Mapping and documenting business lines and processes, outlining projects and business flows, and documenting them. Reviewing key code and database designs in the old system. Any issues or uncertainties should be addressed promptly through communication with relevant personnel from the business side, ensuring that problems are resolved early in the process. Participants: Technical Leader, Architect, Developers Step 4: Database Refactoring If the refactoring involves changes to the database, database refactoring is typically the first step. Many refactoring initiatives are triggered by issues related to the database. During database refactoring, the deficiencies and obstacles in the old system’s database design are addressed. This may involve redesigning tables using normalization or denormalization techniques, considering sharding or partitioning strategies, and more. Participants: DBA, Architect Step 5: Backend System Refactoring Before starting the backend system refactoring, it’s essential to have design and technical documentation in place, as mentioned earlier. Once these documents are finalized through discussions and planning, the architect can proceed with system architecture design, and backend developers can begin coding. This phase is often the most time-consuming and critical part of the refactoring process. The quality of the backend architecture directly affects the success of the refactoring, the quality of the business code, and the overall refactoring quality. Due to the extended timeline of this phase and the fact that its results may not be immediately visible, Agile development methodologies are often used. This allows for iterative development, ensuring effective planning and continuous progress. The advantages of using iterations include: Effective planning and quantification of the entire refactoring process. Visible achievements at each stage, preventing the team from getting stuck in a long refactoring process. The ability to test or observe refactored parts promptly during iterations, allowing continuous learning and improvement. During backend system refactoring, it’s essential to have clear, quantifiable goals and standards. For example, defining the QPS (Queries Per Second) supported by various systems and business modules, the expected response times for interfaces, etc. This enables the team to focus on achieving these goals during refactoring. Regular code reviews should also be conducted throughout the refactoring process to identify and address issues with the refactoring itself and the quality of the code. This helps prevent the introduction of poor designs or subpar code that could harm the entire system. Participants: Technical Leader, Architect, Developers Step 6: Data Migration and Verification If database refactoring is part of the project, data migration becomes a crucial step. It generally involves two types of migration: full migration and incremental migration. Full migration transfers all data from the old system to the new one in one go, while incremental migration handles data created in the old system after full migration until the old system is retired. These migrations are typically scripted or programmed to avoid manual errors. After migration, it’s essential to compare the data between the old and new systems. This comparison can also be automated through scripts or programs to identify discrepancies and perform any necessary adjustments or investigations. Participants: DBA, Developers Step 7: System Validation, Integration, and Testing As the backend system refactoring progresses, scripts and programs should be developed to validate the business interfaces between the old and new systems. This ensures that issues in the refactoring process are detected promptly, and, if necessary, architectural and database adjustments can be made. Additionally, increasing unit test coverage during refactoring is highly beneficial. Once the dependencies between systems and modules are resolved, integration testing can begin. Comprehensive testing, including functional testing, stability testing, performance testing, local testing, and simulating production environments, should be performed. Any issues identified during testing should be addressed, verified, and fixed to meet the standards required for a smooth release. Participants: Architect, Developers, Testers Step 8: Gradual Deployment and Monitoring When the backend system refactoring reaches a certain level of stability, it’s time to initiate gradual deployment. During this phase, only a portion of the traffic is directed to the new system. This allows for real-time tracking and analysis of logs and monitoring alarms. Any issues or anomalies can be addressed promptly. As confidence in the new system’s stability grows, the scope and volume of the deployment can be gradually increased. Continuous monitoring of logs and alarms should be maintained throughout this phase. Participants: DevOps Team, Testers, Developers Step 9: System Transition When it comes to transitioning to the new system, it’s crucial to have a well-defined transition plan in place. This plan should include detailed processes, workflows, and contingency plans, including rollback procedures in case unexpected issues arise. This step ensures that the transition is smooth and minimizes disruption to the business. Participants: DevOps Team, Testers Conclusion After completing the above steps, the system has undergone successful refactoring. However, it’s essential to understand that refactoring is a substantial undertaking, and even after the process, the system may not be flawless. Refactoring is not the endpoint but rather a new beginning.

2023/9/1
articleCard.readMore

《一个地方,人满为患》

里面, 取号, 排队, 叫号, 付费。 一切为了活着。 外面, 工作, 生活, 享乐, 受罪。 活着为了一切。

2023/7/22
articleCard.readMore

《我》

早高峰的洪流中, 我被人群簇拥着, 仿佛生命之王。 我感觉自己, 活着。 但又感觉, 死了。 ChatGPT辅助创作

2023/4/7
articleCard.readMore

How to solve error GitHub Permission denied fatal Could not read from remote repository

There are multiple Git libraries are used on my machine, such as GitHub/Company Private Git repo, etc., the Git tool mainly uses Terminal and GitKraken, and occasionally strange issues will arise. For example, the following error occurred when deploying the Hexo Blog a few days ago: 1 2 3 4 5 Permission denied (publickey). fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. The usual solution is to regenerate the SSH Key of GitHub. But there’s no issue with my local configuration, I can push code normally in Terminal and GitKraken. Guessing that there might be some hitch in the process, I used: 1 ssh -T git@github.com And saw: 1 Hi {username}! You've successfully authenticated, but GitHub does not provide shell access. Then redeployed and pushed, It’s worked! Reference: https://docs.github.com/en/authentication/troubleshooting-ssh/error-permission-denied-publickey

2023/1/1
articleCard.readMore

解决错误 GitHub Permission denied fatal Could not read from remote repository

机器上使用了多个 Git 库,比如 GitHub/公司 Git 库等,Git 工具主要使用 Terminal 和 GitKraken,偶尔会出现奇怪的问题。比如前两天在部署 Hexo Blog 时,遇到以下报错: 1 2 3 4 5 Permission denied (publickey). fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. 通常的解决方案是重新生成 GitHub 的 SSH Key。 但是我本地的配置是没问题的,可以在 Terminal 和GitKraken正常提交代码。 猜想可能是哪个环节不通,于是使用: 1 ssh -T git@github.com 看到: 1 Hi {username}! You've successfully authenticated, but GitHub does not provide shell access. 再重新部署提交,搞定! 参考: https://docs.github.com/en/authentication/troubleshooting-ssh/error-permission-denied-publickey

2023/1/1
articleCard.readMore

《山 · 其二》

任务堆积如山, 债务堆积如山, 茫茫一片看不见。 我, 堆积如山。

2022/12/2
articleCard.readMore

《山 · 其一》

列车从远方, 卸下年轻人, 拉走父母妻儿, 和中老年。 他们身前, 是扬起的帆, 他们身后, 是累累的山。

2022/12/1
articleCard.readMore

《地铁》

一头野兽, 在地下怒吼狂奔, 他吞掉情侣夫妻父母儿女, 吐出一个个沉默的打工人。

2022/11/30
articleCard.readMore

写于11月27日

8年前的一天,在一家粤菜店吃不是饭点的午饭时,我一边品着大麦茶,一边对着左顾右盼着餐厅不多的客人。 餐厅播放着无聊的节目,但当我转头看向电子时钟,上面跳动着猩红色的数字:2014年11月27日。 恍惚间,这年这月这日组成这一串,于我陌生无比,不知是外星数字来了地球,还是我这地球人去了外星。 2-0-1-4,2014年?我印象中并没有这么个年,要说有的,大概是些刚毕业时上大学,刚高考完读高中,刚升高中读初中,升初中时读小学的那些年。 1-1-2-7,11月27日?还早呢。我并没有十一长假,也未曾过过中秋,说起端午,粽子也没吃过,五一呢,我倒没什么记忆深刻的事情。 现在,又是11月27日。

2022/11/27
articleCard.readMore

从优雅地查看K8s应用日志聊到日志管理

曾不知在哪听过一经典名句:程序员的工作有两件事,一是写 Bug,二是找 Bug。 说归说笑归笑,奈何话糙理不糙:在程序员的工作中,Bug的排查分析和解决验证确实占相当可观的一部分时间。 那么说到,而在真正排查 Bug 时,才深刻体会到另一名句:不写日志一时爽,排查 Bug 火Z场。 日志管理,一直是开发人员的老大难题。这个老大难题,大致分为几块内容: 1. 打印日志 狭义上的日志管理,也即打印日志。套用 3W1H 分析方法可以分为几个子问题: Why 为什么要打日志 显而易见,日志是记录关键信息和数据的地方,以备未来排查问题和数据统计分析之用。 What 要打什么样的日志 Where/When 在哪里/什么时候打日志 How 怎么打日志 2. 记录日志 3. 查看日志

2022/10/12
articleCard.readMore

《中国折叠》

我折叠了中国, 因为我想你, 想你到突破几何距离。 广东和江西叠在一起, 你叠进我的身体。 写给一对分隔粤赣两地的恋人

2022/8/18
articleCard.readMore