import { debuglog } from 'node:util';
import { Context } from '@eggjs/core';
import { formatLocale } from '../../utils.js';
const debug = debuglog('@eggjs/i18n/app/extend/context');
export default class I18nContext extends Context {
/**
* get current request locale
* @member Context#locale
* @return {String} lower case locale string, e.g.: 'zh-cn', 'en-us'
*/
get locale(): string {
return this.__getLocale();
}
set locale(l: string) {
this.__setLocale(l);
}
/**
* `ctx.__` 的别名。
* @see {@link Context#__}
* @function Context#gettext
*/
gettext(key: string, value?: any, ...args: any[]) {
return this.app.gettext(this.locale, key, value, ...args);
}
/**
* 如果开启了 I18n 多语言功能,那么会出现此 API,通过它可以获取到当前请求对应的本地化数据。
*
* 详细使用说明,请查看 {@link I18n}
* - `ctx.__ = function (key, value[, value2, ...])`: 类似 `util.format` 接口
* - `ctx.__ = function (key, values)`: 支持数组下标占位符方式,如
* - `__` 的别名是 `gettext(key, value)`
*
* > NOTE: __ 是两个下划线哦!
* @function Context#__
* @example
* ```js
* ctx.__('{0} {0} {1} {1}'), ['foo', 'bar'])
* ctx.gettext('{0} {0} {1} {1}'), ['foo', 'bar'])
* =>
* foo foo bar bar
* ```
* ##### Controller 下的使用示例
*
* ```js
* module.exports = function* () {
* this.body = {
* message: this.__('Welcome back, %s!', this.user.name),
* // 或者使用 gettext,如果觉得 __ 不好看的话
* // message: this.gettext('Welcome back, %s!', this.user.name),
* user: this.user,
* };
* };
* ```
*
* ##### View 文件下的使用示例
*
* ```html
*
{{ __('Email') }}: {{ user.email }}
*
* {{ __('Hello %s, how are you today?', user.name) }}
*
*
* {{ __('{0} {0} {1} {1}'), ['foo', 'bar']) }}
*
* ```
*
* ##### locale 参数获取途径
*
* 优先级从上到下:
*
* - query: `/?locale=en-US`
* - cookie: `locale=zh-TW`
* - header: `Accept-Language: zh-CN,zh;q=0.5`
*/
__(key: string, value?: any, ...args: any[]) {
return this.gettext(key, value, ...args);
}
declare __locale: string;
// 1. query: /?locale=en-US
// 2. cookie: locale=zh-TW
// 3. header: Accept-Language: zh-CN,zh;q=0.5
__getLocale(): string {
if (this.__locale) {
return this.__locale;
}
const { localeAlias, defaultLocale, cookieField, queryField, writeCookie } = this.app.config.i18n;
const cookieLocale = this.cookies.get(cookieField, { signed: false });
// 1. Query
let locale = this.query[queryField] as string;
let localeOrigin = 'query';
// 2. Cookie
if (!locale) {
locale = cookieLocale;
localeOrigin = 'cookie';
}
// 3. Header
if (!locale) {
// Accept-Language: zh-CN,zh;q=0.5
// Accept-Language: zh-CN
let languages = this.acceptsLanguages();
if (languages) {
if (Array.isArray(languages)) {
if (languages[0] === '*') {
languages = languages.slice(1);
}
if (languages.length > 0) {
for (const l of languages) {
const lang = formatLocale(l);
if (this.app.isSupportLocale(lang) || localeAlias[lang]) {
locale = lang;
localeOrigin = 'header';
break;
}
}
}
} else {
locale = languages;
localeOrigin = 'header';
}
}
// all missing, set it to defaultLocale
if (!locale) {
locale = defaultLocale;
localeOrigin = 'default';
}
}
// cookie alias
if (locale in localeAlias) {
const originalLocale = locale;
locale = localeAlias[locale];
debug('Used alias, received %s but using %s', originalLocale, locale);
}
locale = formatLocale(locale);
// validate locale
if (!this.app.isSupportLocale(locale)) {
debug('Locale %s is not supported. Using default (%s)', locale, defaultLocale);
locale = defaultLocale;
}
// if header not send, set the locale cookie
if (writeCookie && cookieLocale !== locale && !this.headerSent) {
updateCookie(this, locale);
}
debug('Locale: %s from %s', locale, localeOrigin);
this.__locale = locale;
this.__localeOrigin = localeOrigin;
return locale;
}
declare __localeOrigin: string;
__getLocaleOrigin() {
if (this.__localeOrigin) {
return this.__localeOrigin;
}
this.__getLocale();
return this.__localeOrigin;
}
__setLocale(locale: string) {
this.__locale = locale;
this.__localeOrigin = 'set';
updateCookie(this, locale);
}
}
function updateCookie(ctx: Context, locale: string) {
const { cookieMaxAge, cookieField, cookieDomain } = ctx.app.config.i18n;
const cookieOptions = {
// make sure browser javascript can read the cookie
httpOnly: false,
maxAge: cookieMaxAge as number,
signed: false,
domain: cookieDomain,
overwrite: true,
};
ctx.cookies.set(cookieField, locale, cookieOptions);
debug('Saved cookie with locale %s, options: %j', locale, cookieOptions);
}