浏览器默认语言与页面访问统计问题二三则
文章目录
- 前言
- 网站默认语言问题
- 网站访问统计问题
- Error: Empty components are self-closing
- Error: A space is required before closing bracket
- 总结
前言
看标题大概能猜到这是一篇杂合体的总结,是这两天处理网站遇到的小问题,怕过段时间再忘了所以总结到一起便于反过来查找问题解决方案,这个问题都是之前未曾接触过的,但遇到他们不用惧怕,只要假装自己能解决就行,这样稳扎稳打的找方案就能解决了。
网站默认语言问题
一个使用Nextjs框架编写的网站,支持中日英三种语言,如果访问主页面时未选择语言,则按照浏览器的语言来选择,这是合理的,一个人如果总用中文的浏览器,那么他在访问一个新网站时较大可能是想看中文的页面,所以我们优先选择当前浏览器的语言即可,默认跳转到指定语言的页面,选择语言的代码如下,是一个中间件 middleware.ts
,问题在于默认语言为日文的浏览器,打开网页是英文的,这就得查查是因为什么了:
import { NextRequest, NextResponse } from 'next/server';
import acceptLanguage from 'accept-language';
import { fallbackLng, languages } from './app/i18n/settings';
acceptLanguage.languages(languages);
export const config = {
// matcher: '/:lng*'
matcher: ['/((?!api|_next/static|_next/image|images|svg|assets|favicon.ico|sw.js).*)']
};
const cookieName = 'i18next';
export function middleware(req: NextRequest) {
let lng;
// if (req.cookies.has(cookieName)) lng = acceptLanguage.get(req.cookies.get(cookieName)?.value);
if (!lng) lng = acceptLanguage.get(req.headers.get('Accept-Language'));
if (!lng) lng = fallbackLng;
// Redirect if lng in path is not supported
if (
!languages.some((loc) => req.nextUrl.pathname.startsWith(`/${loc}`))
&& !req.nextUrl.pathname.startsWith('/_next')
) {
return NextResponse.redirect(new URL(`/${lng}${req.nextUrl.pathname}`, req.url));
}
if (req.headers.has('referer')) {
const refererUrl = new URL(req.headers.get('referer') || 'zh');
const lngInReferer = languages.find((l) => refererUrl.pathname.startsWith(`/${l}`));
const response = NextResponse.next();
if (lngInReferer) response.cookies.set(cookieName, lngInReferer);
return response;
}
return NextResponse.next();
}
其中 matcher
是用来匹配路径的,这个配置表示,中间件会在请求的路径不包含特定字符串时触发。也就是说,路径不能包含指定的 api
、_next/static
等词缀。
网站语言看这一句 if (!lng) lng = acceptLanguage.get(req.headers.get('Accept-Language'));
就行了,它的含义是网站可以根据 Accept-Language 请求头来检测浏览器的首选语言,并返回相应语言的内容。
而我将浏览器默认语言设置为日语时,打开网站传递的 Accept-Language
内容为 ja,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
根据你提供的 Accept-Language
内容 ja,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
,以下是中间件的处理过程:
acceptLanguage.get(req.headers.get('Accept-Language'))
函数会根据 Accept-Language
头部的内容来选择最适合的语言。根据我浏览器传递的内容:
ja
(日语)有最高的优先级。en
(英语)优先级为 0.9。zh-CN
(简体中文)优先级为 0.8。zh
(中文)优先级为 0.7。
由于 ja
的优先级是 1(默认)并且是最优的匹配,所以 acceptLanguage.get()
会选择 ja
作为首选语言。
但是可以还取决于 languages
的值,因为 acceptLanguage
是通过 languages
完成初始化的,而我查询了项目中它的值为 export const languages = ['jp', 'en', 'zh'];
,发现问题了没有?日语浏览器将 ja
作为最优先展示语言,但是备选项里没有 ja
,所以最终选出的结果是 en
,这也就解释了为什么日语浏览器默认打开网页时显示英文页面的现象了。
日文的缩写通常是 “ja”,而不是 “jp”。
- “ja” 是 ISO 639-1 标准中日语的语言代码。
- “jp” 通常用来表示 日本(Japan)的国家代码,在 ISO 3166-1 标准中使用。
因此,在国际化(i18n)和多语言网站的语言代码中,日文通常使用 “ja”。
只要原因后就好修改了,改成ja也好,保持jp也罢,修正逻辑按照一致的顺序处理,问题也就能解决了。
网站访问统计问题
如果是一个网站开发者遇到这种问题,应该会有一个完整的解决方案,而我这种半吊子的网页开发人员,只想尽快的解决问题,所以我的搜索方向就是怎么尽快解决这个问题。起初搜到的方案是使用 Vercel Analytics
来查看,初始有个免费额度,超过25,000 events要进行收费,因为我的网站就部署在 Vercel 上,所以这种方案应该是最融洽的,查看每月的 events 使用量估算了费用,每月要 $100 以上,纠结中。
然后就搜到了 Google Analytics
,介绍里说它完全免费,应用广泛,但是最近评价却不好,一方面是指责它收集隐私信息,另一方面是说它被很多浏览器插件屏蔽,导致无法准确统计,在抵制它的同时很多人都推荐使用了 Umami
。
Umami
是一个开源的简单、隐私友好的网站分析工具。它提供了类似于 Google Analytics 的功能,但更加专注于数据隐私,且无需依赖第三方 cookies。这使得 Umami 成为那些希望追踪用户活动但又需要遵守严格隐私法律(如 GDPR 或 CCPA)的个人和企业的理想选择。
Umami
是完全开源的(使用 MIT 许可证),代码可以在 GitHub 上找到并自行部署。客户端脚本文件仅约 2 KB,加载速度快,不会影响网页性能。时查看访问者活动,例如当前在线人数、浏览器、设备、地域等信息。
后端:Node.js 和 PostgreSQL。前端:基于 React 构建。部署:支持 Docker,配置简单。技术栈提起来挺简单,但是像我这么懒的人现在还不打算配置一遍,自己配置还要买服务器和数据库,因为我查到它有一个 Umami Cloud
。
Umami Cloud 是 Umami
的云托管服务,提供了一个简单、隐私友好的网站分析工具的在线版本。它允许用户通过 Umami 提供的 Web 界面,无需自行托管和管理服务器即可使用该分析工具。相比自己搭建 Umami,使用 Umami Cloud 可以节省大量的部署和维护成本,适合不想处理服务器配置的用户。
用户无需担心部署和维护服务器,Umami Cloud 提供了一个即开即用的服务,只需要注册并配置网站即可开始使用。与自托管的 Umami 一样,Umami Cloud 注重用户隐私,不收集个人可识别信息。它也不依赖 cookies 或第三方追踪,因此完全符合 GDPR 和其他隐私法规。
用户只需要将提供的 JavaScript 跟踪代码插入到网站页面,不需要任何后端代码,适合非技术用户。Umami Cloud 提供了不同的定价套餐,包括免费和付费版本。免费版本适合小型网站或个人使用,付费版本提供更多的功能和数据存储选项。
看起来数据大了还是得付钱,免费版本限制 100K events per month,Up to 3 websites,6 month data retention,对于我的网站来说还是不够用,但付费的价格相比Vercel算是降了不少,所以我把它的 跟踪代码 胡乱的集成到了我项目的 <head>...</head>
中,进而引发了后面的问题
<script defer src="https://cloud.umami.is/script.js" data-website-id="YOUR_WEBSITE_ID"></script>
Error: Empty components are self-closing
这个错误的完整显示如下:
Failed to compile.
30:13 Error: Empty components are self-closing react/self-closing-comp
info - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/basic-features/eslint#disabling-rules
ELIFECYCLE Command failed with exit code 1.
Error: Command "pnpm run build" exited with 1
我的代码开始写成了这样:
export default function RootLayout({
children,
params: { lng },
}: {
children: React.ReactNode;
params: { lng: LanguageType };
}) {
return (
<html lang={lng} className={myFont.variable}>
<head><script defer src="https://cloud.umami.is/script.js" data-website-id="YOUR_WEBSITE_ID"></script></head>
<body className="relative font-my">{children}</body>
</html>
);
}
看着ChatGPT的解释我陷入了沉思,不过后来我明白了它的意思,这个错误是由于 ESLint 中的 react/self-closing-comp
规则引起的,该规则要求空的组件标签必须是自闭合的。
解决方法是检查报错所在的代码(通常是在第 30 行第 13 列),很可能是你有一个空的元素,例如:
<div></div>
它应该改成自闭合的写法:
<div />
所以我把代码改成了这样 <head><script defer src="https://cloud.umami.is/script.js" data-website-id="YOUR_WEBSITE_ID"/></head>
,去掉了 </script>
然后就报了下面这个错。
Error: A space is required before closing bracket
完整错误如下:
Failed to compile.
30:120 Error: A space is required before closing bracket react/jsx-tag-spacing
info - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/basic-features/eslint#disabling-rules
ELIFECYCLE Command failed with exit code 1.
Error: Command "pnpm run build" exited with 1
这次我还是没反应过来,经过ChatGPT解释发现,之前就已经给出准备的例子了,这个新的错误是由 ESLint 的 react/jsx-tag-spacing 规则引起的,它要求在自闭合标签的闭合括号前留一个空格。所以最终改成下面这样就没问题了
<head><script defer src="https://cloud.umami.is/script.js" data-website-id="YOUR_WEBSITE_ID" /></head>
真是一个空格也不能错啊~
总结
- 网站的默认语言可以根据浏览器的设置语言来初始化,获取代码简化为
acceptLanguage.get(req.headers.get('Accept-Language'));
- 网页统计可以使用
Vercel Analytics
、Google Analytics
或者Umami
Vercel Analytics
与 Vercel 兼容性好,毕竟出自同一家班底,配合会比较顺畅,应该还会有自家产品的关联优化Google Analytics
依赖 cookies,可能涉及隐私问题,数据存储在 Google 的服务器,由 Google 管理,功能全面,适合复杂需求Umami
完全隐私友好,无 cookies,核心功能足够,但比 Google Analytics 少,可以用户自行托管,完全掌控
他日若是同淋雪,也算此生共白头。在两个人维系的关系中,一方的忍耐退让并不会让另一方生出善意,反而会导致对方以为之前的所有都是理所应当,进而变本加厉。总会有两个人不在一个频道上,你给她讲理,她跟你讲情,你跟她讲情,她跟你讲法,你跟她讲法,他跟你讲理,反正你永远对不了~