@qlover/create-app 0.7.14 → 0.7.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/next-app/README.en.md +131 -0
- package/dist/templates/next-app/README.md +115 -20
- package/dist/templates/next-app/docs/en/api.md +387 -0
- package/dist/templates/next-app/docs/en/component.md +544 -0
- package/dist/templates/next-app/docs/en/database.md +496 -0
- package/dist/templates/next-app/docs/en/development-guide.md +727 -0
- package/dist/templates/next-app/docs/en/env.md +563 -0
- package/dist/templates/next-app/docs/en/i18n.md +287 -0
- package/dist/templates/next-app/docs/en/index.md +166 -0
- package/dist/templates/next-app/docs/en/page.md +457 -0
- package/dist/templates/next-app/docs/en/project-structure.md +177 -0
- package/dist/templates/next-app/docs/en/router.md +427 -0
- package/dist/templates/next-app/docs/en/theme.md +532 -0
- package/dist/templates/next-app/docs/en/validator.md +478 -0
- package/dist/templates/next-app/docs/zh/api.md +387 -0
- package/dist/templates/next-app/docs/zh/component.md +544 -0
- package/dist/templates/next-app/docs/zh/database.md +496 -0
- package/dist/templates/next-app/docs/zh/development-guide.md +727 -0
- package/dist/templates/next-app/docs/zh/env.md +563 -0
- package/dist/templates/next-app/docs/zh/i18n.md +287 -0
- package/dist/templates/next-app/docs/zh/index.md +166 -0
- package/dist/templates/next-app/docs/zh/page.md +457 -0
- package/dist/templates/next-app/docs/zh/project-structure.md +177 -0
- package/dist/templates/next-app/docs/zh/router.md +427 -0
- package/dist/templates/next-app/docs/zh/theme.md +532 -0
- package/dist/templates/next-app/docs/zh/validator.md +476 -0
- package/package.json +1 -1
- package/dist/templates/next-app/docs/env.md +0 -94
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# 国际化 (i18n) 指南
|
|
2
|
+
|
|
3
|
+
本文档将详细介绍项目中的国际化实现,包括 API 错误信息、页面内容以及所有需要多语言支持的文本内容。
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
- [基础配置](#基础配置)
|
|
8
|
+
- [API 错误信息处理](#api-错误信息处理)
|
|
9
|
+
- [页面内容国际化](#页面内容国际化)
|
|
10
|
+
- [使用规范](#使用规范)
|
|
11
|
+
|
|
12
|
+
## 基础配置
|
|
13
|
+
|
|
14
|
+
项目使用 next-intl 进行国际化管理,配置文件位于 `config/i18n/i18nConfig.ts`:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
export const i18nConfig = {
|
|
18
|
+
localeNames: {
|
|
19
|
+
en: 'English',
|
|
20
|
+
zh: '中文'
|
|
21
|
+
},
|
|
22
|
+
fallbackLng: 'en',
|
|
23
|
+
supportedLngs: ['en', 'zh'],
|
|
24
|
+
localeDetection: true
|
|
25
|
+
};
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
所有支持的语言都在 `supportedLngs` 中定义,默认语言为 `fallbackLng` 指定的语言。
|
|
29
|
+
|
|
30
|
+
## API 错误信息处理
|
|
31
|
+
|
|
32
|
+
API 错误信息的国际化处理流程如下:
|
|
33
|
+
|
|
34
|
+
1. API 响应格式:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
interface AppApiErrorInterface {
|
|
38
|
+
success: false;
|
|
39
|
+
id: string; // i18n key
|
|
40
|
+
message?: string; // 可选的直接显示消息
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
2. 错误处理流程:
|
|
45
|
+
- API 请求通过 `AppApiPlugin` 处理响应
|
|
46
|
+
- 如果响应表示错误,错误信息会被转换为 `ExecutorError`
|
|
47
|
+
- `DialogErrorPlugin` 负责显示错误消息
|
|
48
|
+
- 如果错误消息是 i18n key 格式(如 "namespace_key"),则会通过 i18n 服务翻译后显示
|
|
49
|
+
- 如果不是 i18n key 格式,则直接显示原始消息
|
|
50
|
+
|
|
51
|
+
示例:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// API 返回错误
|
|
55
|
+
{
|
|
56
|
+
success: false,
|
|
57
|
+
id: "error_invalid_password"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// DialogErrorPlugin 会自动翻译并显示对应语言的错误信息
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 页面内容国际化
|
|
64
|
+
|
|
65
|
+
### PageI18nInterface
|
|
66
|
+
|
|
67
|
+
`PageI18nInterface` 是一个核心接口,用于定义页面的国际化内容和 SEO 元数据。这个接口提供了完整的页面元数据结构,包括基本 SEO 信息、Open Graph、Twitter Card 等。
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
interface PageI18nInterface {
|
|
71
|
+
// 基本元数据属性
|
|
72
|
+
title: string; // 页面标题
|
|
73
|
+
description: string; // 页面描述
|
|
74
|
+
content: string; // 页面内容
|
|
75
|
+
keywords: string; // 页面关键词
|
|
76
|
+
|
|
77
|
+
// 规范链接
|
|
78
|
+
canonical?: string; // 可选的规范链接
|
|
79
|
+
|
|
80
|
+
// 爬虫控制
|
|
81
|
+
robots?: {
|
|
82
|
+
index?: boolean; // 是否允许索引
|
|
83
|
+
follow?: boolean; // 是否跟随链接
|
|
84
|
+
noarchive?: boolean; // 是否允许存档
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Open Graph 元数据
|
|
88
|
+
og?: {
|
|
89
|
+
title?: string; // OG 标题
|
|
90
|
+
description?: string; // OG 描述
|
|
91
|
+
type?: string; // 内容类型
|
|
92
|
+
image?: string; // 图片 URL
|
|
93
|
+
url?: string; // 页面 URL
|
|
94
|
+
siteName?: string; // 站点名称
|
|
95
|
+
locale?: string; // 语言地区
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Twitter Card 元数据
|
|
99
|
+
twitter?: {
|
|
100
|
+
card?: 'summary' | 'summary_large_image' | 'app' | 'player'; // 卡片类型
|
|
101
|
+
site?: string; // Twitter 站点
|
|
102
|
+
creator?: string; // 创建者
|
|
103
|
+
title?: string; // Twitter 标题
|
|
104
|
+
description?: string; // Twitter 描述
|
|
105
|
+
image?: string; // Twitter 图片
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// 附加元数据
|
|
109
|
+
author?: string; // 作者
|
|
110
|
+
publishedTime?: string; // 发布时间
|
|
111
|
+
modifiedTime?: string; // 修改时间
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
使用说明:
|
|
116
|
+
|
|
117
|
+
1. 所有字符串类型的值都可以是:
|
|
118
|
+
- i18n key:将会被翻译成当前语言
|
|
119
|
+
- 直接字符串:将直接使用,不进行翻译
|
|
120
|
+
2. 可选字段(带 `?` 的字段)表示该页面可以不包含这些元数据
|
|
121
|
+
3. 这个接口通常用于:
|
|
122
|
+
- 定义页面的 SEO 信息
|
|
123
|
+
- 生成 meta 标签
|
|
124
|
+
- 提供社交媒体分享信息
|
|
125
|
+
- 控制搜索引擎爬虫行为
|
|
126
|
+
|
|
127
|
+
### 在页面中使用 PageI18nInterface
|
|
128
|
+
|
|
129
|
+
以下是一个完整的页面示例,展示如何使用 PageI18nInterface:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// 1. 首先定义页面的 i18n 接口
|
|
133
|
+
export const homeI18n = Object.freeze({
|
|
134
|
+
title: i18nKeys.PAGE_HOME_TITLE,
|
|
135
|
+
description: i18nKeys.PAGE_HOME_DESCRIPTION,
|
|
136
|
+
content: i18nKeys.PAGE_HOME_DESCRIPTION,
|
|
137
|
+
keywords: i18nKeys.PAGE_HOME_KEYWORDS,
|
|
138
|
+
|
|
139
|
+
welcome: i18nKeys.HOME_WELCOME,
|
|
140
|
+
getStartedTitle: i18nKeys.HOME_GET_STARTED_TITLE,
|
|
141
|
+
getStartedDescription: i18nKeys.HOME_GET_STARTED_DESCRIPTION,
|
|
142
|
+
getStartedButton: i18nKeys.HOME_GET_STARTED_BUTTON
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// 2. 在页面中使用
|
|
146
|
+
export async function generateMetadata({ params }: { params: PageParamsType }): Promise<Metadata> {
|
|
147
|
+
const pageParams = new PageParams(await params);
|
|
148
|
+
// 获取页面 SEO 元数据
|
|
149
|
+
return await pageParams.getI18nInterface(homeI18n);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default async function HomePage({ params }: PageParamsProps) {
|
|
153
|
+
const pageParams = new PageParams(await params!);
|
|
154
|
+
// 获取页面翻译内容
|
|
155
|
+
const tt = await pageParams.getI18nInterface(homeI18n);
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<BaseLayout>
|
|
159
|
+
<section>
|
|
160
|
+
<h1>{tt.welcome}</h1>
|
|
161
|
+
<p>{tt.description}</p>
|
|
162
|
+
<div>
|
|
163
|
+
<h2>{tt.getStartedTitle}</h2>
|
|
164
|
+
<p>{tt.getStartedDescription}</p>
|
|
165
|
+
<Button>{tt.getStartedButton}</Button>
|
|
166
|
+
</div>
|
|
167
|
+
</section>
|
|
168
|
+
</BaseLayout>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
这个示例展示了:
|
|
174
|
+
|
|
175
|
+
1. 如何定义页面特定的 i18n 接口
|
|
176
|
+
2. 如何使用 `getI18nInterface` 获取翻译后的内容
|
|
177
|
+
3. 如何在页面元数据中使用 i18n
|
|
178
|
+
4. 如何在页面组件中使用翻译的内容
|
|
179
|
+
|
|
180
|
+
### 实现页面国际化
|
|
181
|
+
|
|
182
|
+
页面内容的国际化主要通过以下方式实现:
|
|
183
|
+
|
|
184
|
+
1. 页面 i18n 接口定义:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// config/i18n/HomeI18n.ts
|
|
188
|
+
export const homeI18n = Object.freeze({
|
|
189
|
+
title: i18nKeys.PAGE_HOME_TITLE,
|
|
190
|
+
description: i18nKeys.PAGE_HOME_DESCRIPTION,
|
|
191
|
+
welcome: i18nKeys.HOME_WELCOME
|
|
192
|
+
// ...
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
2. 获取翻译内容:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// 在页面组件中
|
|
200
|
+
const messages = await getMessages({ locale });
|
|
201
|
+
// 或者使用 PageParams
|
|
202
|
+
const i18nContent = await pageParams.getI18nInterface(homeI18n);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
3. 动态路由中的语言处理:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
// src/app/[locale]/layout.tsx
|
|
209
|
+
export default async function RootLayout({ children, params }) {
|
|
210
|
+
const pageParams = new PageParams(params);
|
|
211
|
+
const locale = pageParams.getI18nWithNotFound();
|
|
212
|
+
// ...
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## 使用规范
|
|
217
|
+
|
|
218
|
+
1. **所有文本内容必须使用 i18n key**:
|
|
219
|
+
- 不要直接在代码中写入固定文本
|
|
220
|
+
- 所有文本都应该定义在 i18n 配置文件中
|
|
221
|
+
- 使用有意义的 key 名称,如:`PAGE_HOME_TITLE`、`ERROR_INVALID_INPUT`
|
|
222
|
+
|
|
223
|
+
2. **i18n key 命名规范**:
|
|
224
|
+
- 使用大写字母和下划线
|
|
225
|
+
- 使用有意义的前缀表示分类,如:
|
|
226
|
+
- `PAGE_` 前缀表示页面相关
|
|
227
|
+
- `ERROR_` 前缀表示错误信息
|
|
228
|
+
- `COMMON_` 前缀表示通用文本
|
|
229
|
+
|
|
230
|
+
3. **翻译文件组织**:
|
|
231
|
+
- 翻译文件位于 `public/locales/` 目录
|
|
232
|
+
- 按语言代码分类:`en.json`、`zh.json`
|
|
233
|
+
- 保持所有语言文件的 key 一致
|
|
234
|
+
|
|
235
|
+
4. **类型安全**:
|
|
236
|
+
- 使用 TypeScript 接口定义 i18n 内容
|
|
237
|
+
- 所有 i18n key 都应该在 `config/Identifier/` 目录下定义
|
|
238
|
+
- 使用 `Object.freeze` 确保 i18n 对象不被修改
|
|
239
|
+
|
|
240
|
+
5. **最佳实践**:
|
|
241
|
+
- 使用 `PageParams` 类处理页面的语言相关逻辑
|
|
242
|
+
- 在组件中使用 `useTranslations` hook 获取翻译
|
|
243
|
+
- API 错误信息优先使用 i18n key,而不是硬编码的消息
|
|
244
|
+
- 确保所有用户可见的文本都支持多语言
|
|
245
|
+
|
|
246
|
+
## 示例
|
|
247
|
+
|
|
248
|
+
1. 页面组件中使用 i18n:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
export function LoginForm(props: { tt: LoginI18nInterface }) {
|
|
252
|
+
const { tt } = props;
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<form>
|
|
256
|
+
<h1>{tt.title}</h1>
|
|
257
|
+
<p>{tt.description}</p>
|
|
258
|
+
{/* ... */}
|
|
259
|
+
</form>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
2. API 错误处理:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// 后端返回错误
|
|
268
|
+
throw new AppErrorApi({
|
|
269
|
+
id: 'ERROR_INVALID_CREDENTIALS',
|
|
270
|
+
success: false
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// 前端自动处理并显示翻译后的错误消息
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
3. 动态内容:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
const t = await getTranslations({
|
|
280
|
+
locale: pageParams.getLocale(),
|
|
281
|
+
namespace: 'common'
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const message = t('welcome', {
|
|
285
|
+
name: userName
|
|
286
|
+
});
|
|
287
|
+
```
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Next.js 全栈应用开发文档
|
|
2
|
+
|
|
3
|
+
## 简介
|
|
4
|
+
|
|
5
|
+
这是一个基于 Next.js 的全栈应用模板,采用面向接口的设计模式,实现了清晰的分层架构。本模板提供了完整的全栈开发解决方案,包括服务端API、客户端状态管理、认证授权、国际化等核心功能。
|
|
6
|
+
|
|
7
|
+
### 主要特性
|
|
8
|
+
|
|
9
|
+
- 🏗️ **全栈架构**:基于 Next.js 的应用路由和API路由
|
|
10
|
+
- 🔌 **接口驱动**:完整的面向接口编程实践
|
|
11
|
+
- 🎨 **主题定制**:基于 Tailwind CSS 的主题系统
|
|
12
|
+
- 🌍 **国际化**:基于 next-intl 的多语言支持
|
|
13
|
+
- 🔄 **依赖注入**:基于 TypeScript 的 IOC 容器
|
|
14
|
+
- 🛡️ **身份认证**:完整的认证和授权方案
|
|
15
|
+
- 📡 **分层设计**:清晰的前后端分层架构
|
|
16
|
+
- 🎮 **状态管理**:基于控制器模式的状态管理
|
|
17
|
+
- 🔗 **数据访问**:灵活的数据库访问层
|
|
18
|
+
|
|
19
|
+
## 快速开始
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 安装依赖
|
|
23
|
+
pnpm install
|
|
24
|
+
|
|
25
|
+
# 开发环境启动
|
|
26
|
+
pnpm dev
|
|
27
|
+
# cross-env APP_ENV=localhost next dev --turbopack --port 3100
|
|
28
|
+
# 自动加载 .env.localhost -> .env
|
|
29
|
+
|
|
30
|
+
# 预发环境启动
|
|
31
|
+
pnpm dev:staging
|
|
32
|
+
# cross-env APP_ENV=staging next dev --turbopack --port 3100
|
|
33
|
+
# 自动加载 .env.staging -> .env
|
|
34
|
+
|
|
35
|
+
# 构建生产版本
|
|
36
|
+
pnpm build
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 架构概览
|
|
40
|
+
|
|
41
|
+
### 1. 客户端架构
|
|
42
|
+
|
|
43
|
+
#### 接口定义 (src/base/port)
|
|
44
|
+
|
|
45
|
+
- AppUserApiInterface: 用户API接口
|
|
46
|
+
- AdminPageInterface: 管理页面接口
|
|
47
|
+
- AsyncStateInterface: 异步状态接口
|
|
48
|
+
- RouterInterface: 路由管理接口
|
|
49
|
+
|
|
50
|
+
#### 核心实现
|
|
51
|
+
|
|
52
|
+
- 控制器层:状态和业务逻辑管理
|
|
53
|
+
- 服务层:API调用和数据处理
|
|
54
|
+
- UI组件:可复用的展示组件
|
|
55
|
+
|
|
56
|
+
### 2. 服务端架构
|
|
57
|
+
|
|
58
|
+
#### 接口定义 (src/server/port)
|
|
59
|
+
|
|
60
|
+
- ServerAuthInterface: 认证接口
|
|
61
|
+
- DBBridgeInterface: 数据库操作接口
|
|
62
|
+
- UserRepositoryInterface: 用户仓库接口
|
|
63
|
+
- ValidatorInterface: 数据验证接口
|
|
64
|
+
|
|
65
|
+
#### 核心实现
|
|
66
|
+
|
|
67
|
+
- API路由层:请求处理和响应
|
|
68
|
+
- 服务层:业务逻辑实现
|
|
69
|
+
- 数据访问层:仓库和数据库交互
|
|
70
|
+
|
|
71
|
+
## 开发指南
|
|
72
|
+
|
|
73
|
+
### 1. API 开发流程
|
|
74
|
+
|
|
75
|
+
1. 定义接口 (src/server/port)
|
|
76
|
+
2. 实现验证器 (src/server/validators)
|
|
77
|
+
3. 实现服务层 (src/server/services)
|
|
78
|
+
4. 实现数据访问 (src/server/repositorys)
|
|
79
|
+
5. 创建API路由 (src/app/api)
|
|
80
|
+
|
|
81
|
+
### 2. 页面开发流程
|
|
82
|
+
|
|
83
|
+
1. 创建页面组件 (src/app/[locale])
|
|
84
|
+
2. 实现页面控制器
|
|
85
|
+
3. 添加API服务
|
|
86
|
+
4. 注册IOC容器
|
|
87
|
+
|
|
88
|
+
### 3. 最佳实践
|
|
89
|
+
|
|
90
|
+
#### 接口优先开发
|
|
91
|
+
|
|
92
|
+
- 先定义接口,再实现具体类
|
|
93
|
+
- 保持接口的单一职责
|
|
94
|
+
- 使用依赖注入管理依赖
|
|
95
|
+
|
|
96
|
+
#### 分层原则
|
|
97
|
+
|
|
98
|
+
- 保持层级间的清晰边界
|
|
99
|
+
- 通过接口进行层级间通信
|
|
100
|
+
- 避免跨层级直接调用
|
|
101
|
+
|
|
102
|
+
#### 状态管理
|
|
103
|
+
|
|
104
|
+
- 使用控制器管理复杂状态
|
|
105
|
+
- 保持状态的不可变性
|
|
106
|
+
- 统一的状态更新流程
|
|
107
|
+
|
|
108
|
+
## 核心功能文档
|
|
109
|
+
|
|
110
|
+
### 基础架构
|
|
111
|
+
|
|
112
|
+
- [项目结构说明](./project-structure.md)
|
|
113
|
+
- [开发规范指南](./development-guide.md)
|
|
114
|
+
- [环境配置指南](./env.md)
|
|
115
|
+
|
|
116
|
+
### 服务端开发
|
|
117
|
+
|
|
118
|
+
- [API开发指南](./api.md)
|
|
119
|
+
- [认证授权](./auth.md)
|
|
120
|
+
- [数据访问层](./database.md)
|
|
121
|
+
- [验证器开发](./validator.md)
|
|
122
|
+
|
|
123
|
+
### 客户端开发
|
|
124
|
+
|
|
125
|
+
- [页面开发指南](./page.md)
|
|
126
|
+
- [组件开发](./component.md)
|
|
127
|
+
|
|
128
|
+
### 功能模块
|
|
129
|
+
|
|
130
|
+
- [国际化开发](./i18n.md)
|
|
131
|
+
- [主题系统](./theme.md)
|
|
132
|
+
- [路由管理](./router.md)
|
|
133
|
+
|
|
134
|
+
## 常见问题
|
|
135
|
+
|
|
136
|
+
### 1. 开发环境配置
|
|
137
|
+
|
|
138
|
+
- Node.js >= 16
|
|
139
|
+
- pnpm >= 8.0
|
|
140
|
+
- 推荐使用 VSCode + 推荐插件
|
|
141
|
+
|
|
142
|
+
### 2. 开发相关
|
|
143
|
+
|
|
144
|
+
- 接口设计规范
|
|
145
|
+
- 依赖注入使用
|
|
146
|
+
- 控制器开发规范
|
|
147
|
+
|
|
148
|
+
### 3. 部署相关
|
|
149
|
+
|
|
150
|
+
- 环境变量配置
|
|
151
|
+
- 构建优化
|
|
152
|
+
- 部署流程
|
|
153
|
+
|
|
154
|
+
## 更新日志
|
|
155
|
+
|
|
156
|
+
查看 [CHANGELOG.md](../../CHANGELOG.md) 了解详细的更新历史。
|
|
157
|
+
|
|
158
|
+
## 支持和帮助
|
|
159
|
+
|
|
160
|
+
- 提交 Issue
|
|
161
|
+
- 查看 Wiki
|
|
162
|
+
- 参与讨论
|
|
163
|
+
|
|
164
|
+
## 许可证
|
|
165
|
+
|
|
166
|
+
本项目基于 [ISC 许可证](../../LICENSE) 开源。
|