@elf-express/admin-net-mcp 1.0.0 → 1.1.0

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.
Files changed (55) hide show
  1. package/dist/index.js +83 -88
  2. package/knowledge/Furion_Teaching_Manual/04-1-/351/205/215/347/275/256.md +442 -0
  3. package/knowledge/Furion_Teaching_Manual/04-2-/351/201/270/351/240/205.md +363 -0
  4. package/knowledge/Furion_Teaching_Manual/05-1-/345/213/225/346/205/213WebAPI.md +825 -0
  5. package/knowledge/Furion_Teaching_Manual/05-2-HttpContext.md +217 -0
  6. package/knowledge/Furion_Teaching_Manual/05-3-/347/257/251/351/201/270/345/231/250/346/224/224/346/210/252/345/231/250AOP.md +581 -0
  7. package/knowledge/Furion_Teaching_Manual/05-4-/350/253/213/346/261/202/347/250/275/346/240/270/346/227/245/350/252/214.md +129 -0
  8. package/knowledge/Furion_Teaching_Manual/05-5-/344/270/255/344/273/213/350/273/237/351/253/224Middleware.md +328 -0
  9. package/knowledge/Furion_Teaching_Manual/05-6-Vue-React-Angular/344/273/213/351/235/242/344/273/243/347/220/206.md +317 -0
  10. package/knowledge/Furion_Teaching_Manual/06-1/350/246/217/347/257/204/345/214/226/346/216/245/345/217/243.md +1458 -0
  11. package/knowledge/Furion_Teaching_Manual/06-2/347/254/254/344/270/211/346/226/271API_Scalar.md +91 -0
  12. package/knowledge/Furion_Teaching_Manual/07-/345/217/213/345/245/275/344/276/213/345/244/226/350/231/225/347/220/206.md +511 -0
  13. package/knowledge/Furion_Teaching_Manual/08-1-/350/263/207/346/226/231/351/251/227/350/255/211/345/237/272/347/244/216/344/275/277/347/224/250.md +587 -0
  14. package/knowledge/Furion_Teaching_Manual/10-1-SqlSugar/346/225/264/345/220/210.md +336 -0
  15. package/knowledge/Furion_Teaching_Manual/11-SaaS /345/244/232/347/247/237/346/210/266/347/255/206/350/250/230.md" +271 -0
  16. package/knowledge/Furion_Teaching_Manual/12-furion-dependency-injection.md +408 -0
  17. package/knowledge/Furion_Teaching_Manual/13-/347/211/251/344/273/266/350/263/207/346/226/231/346/230/240/345/260/204/357/274/210Mapster/357/274/211.md +162 -0
  18. package/knowledge/Furion_Teaching_Manual/14-/345/210/206/345/270/203/345/274/217/347/274/223/345/255/230.md +311 -0
  19. package/knowledge/Furion_Teaching_Manual/15-/345/256/211/345/205/250/351/211/264/346/235/203.md +832 -0
  20. package/knowledge/Furion_Teaching_Manual/17-/346/252/242/350/246/226/347/257/204/346/234/254/345/274/225/346/223/216.md +327 -0
  21. package/knowledge/Furion_Teaching_Manual/18-/346/227/245/350/252/214/350/250/230/351/214/204.md +639 -0
  22. package/knowledge/Furion_Teaching_Manual/19-1-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/345/237/272/347/244/216/344/275/277/347/224/250.md +621 -0
  23. package/knowledge/Furion_Teaching_Manual/19-2-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/351/200/262/351/232/216/346/214/207/345/215/227.md +928 -0
  24. package/knowledge/Furion_Teaching_Manual/19-3-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/345/270/270/350/246/213/345/225/217/351/241/214.md +362 -0
  25. package/knowledge/Furion_Teaching_Manual/20-/350/263/207/346/226/231/345/212/240/350/247/243/345/257/206.md +286 -0
  26. package/knowledge/Furion_Teaching_Manual/20-/351/231/204/351/214/204-KSort/350/263/207/346/226/231/347/260/275/345/220/215/345/256/214/346/225/264/345/216/237/345/247/213/347/242/274.md +305 -0
  27. package/knowledge/Furion_Teaching_Manual/21-/345/205/250/347/220/203/345/214/226/345/222/214/346/234/254/345/234/260/345/214/226.md +342 -0
  28. package/knowledge/Furion_Teaching_Manual/22-/344/272/213/344/273/266/345/214/257/346/265/201/346/216/222EventBus.md +448 -0
  29. package/knowledge/Furion_Teaching_Manual/23-JSON/345/272/217/345/210/227/345/214/226.md +340 -0
  30. package/knowledge/Furion_Teaching_Manual/24-/345/215/263/346/231/202/351/200/232/350/250/212SignalR.md +247 -0
  31. package/knowledge/Furion_Teaching_Manual/25-/350/274/224/345/212/251/350/247/222/350/211/262/346/234/215/345/213/231WorkerService.md +295 -0
  32. package/knowledge/Furion_Teaching_Manual/26-1-/346/216/222/347/250/213/344/275/234/346/245/255/345/256/232/346/231/202/344/273/273/345/213/231.md +631 -0
  33. package/knowledge/Furion_Teaching_Manual/26-2-Cron/350/241/250/351/201/224/345/274/217.md +203 -0
  34. package/knowledge/Furion_Teaching_Manual/26-3-/344/273/273/345/213/231/344/275/207/345/210/227TaskQueue.md +215 -0
  35. package/knowledge/Furion_Teaching_Manual/27-/345/210/206/346/225/243/345/274/217ID/347/224/237/346/210/220.md +86 -0
  36. package/knowledge/Furion_Teaching_Manual/28-/346/250/241/347/265/204/345/214/226/351/226/213/347/231/274.md +68 -0
  37. package/knowledge/Furion_Teaching_Manual/29-/346/265/201/350/256/212/347/211/251/344/273/266Clay.md +313 -0
  38. package/knowledge/Furion_Teaching_Manual/30-/350/204/253/346/225/217/350/231/225/347/220/206/357/274/210Sensitive Detection).md" +168 -0
  39. package/knowledge/Furion_Teaching_Manual/32-/346/234/203/350/251/261/345/222/214/347/213/200/346/205/213/347/256/241/347/220/206.md +147 -0
  40. package/knowledge/Furion_Teaching_Manual/33-IPC/347/250/213/345/272/217/351/200/232/350/250/212.md +98 -0
  41. package/knowledge/Furion_Teaching_Manual/34-2-Docker/351/203/250/347/275/262.md +313 -0
  42. package/knowledge/Furion_Teaching_Manual/34-3-Nginx/351/203/250/347/275/262.md +157 -0
  43. package/knowledge/Furion_Teaching_Manual/34-8-WindowsService/351/203/250/347/275/262.md +112 -0
  44. package/knowledge/Furion_Teaching_Manual/35-1-Docker/347/222/260/345/242/203/346/214/201/347/272/214/351/203/250/347/275/262Jenkins.md +169 -0
  45. package/knowledge/Furion_Teaching_Manual/36-1-/345/226/256/345/205/203/346/270/254/350/251/246/346/225/264/345/220/210/346/270/254/350/251/246.md +275 -0
  46. package/knowledge/Furion_Teaching_Manual/36-3-/345/237/272/346/272/226/346/270/254/350/251/246Benchmarking.md +80 -0
  47. package/knowledge/attributes.md +153 -0
  48. package/knowledge/config.md +147 -0
  49. package/knowledge/entity.md +115 -0
  50. package/knowledge/event.md +124 -0
  51. package/knowledge/plugin.md +136 -0
  52. package/knowledge/service.md +146 -0
  53. package/knowledge/sqlsugar.md +172 -0
  54. package/knowledge/vue-typescript.md +338 -0
  55. package/package.json +3 -2
@@ -0,0 +1,305 @@
1
+ # KSort API 資料簽名 — 完整原始碼參考
2
+
3
+ > Furion 4.9.5.10+ 內建,用於前後端資料完整性驗證,防止資料被篡改。
4
+
5
+ ---
6
+
7
+ ## 演算法原理
8
+
9
+ 1. 將 `app_id`、`app_key`、`command`、`data`、`timestamp` 組成字典
10
+ 2. 按 key 字母順序排序(KSort)
11
+ 3. 以半形逗號連接為 `key=value,key=value,...` 格式
12
+ 4. 對連接結果進行 MD5 加密(32 位小寫)作為 `signature`
13
+
14
+ ---
15
+
16
+ ## C# 後端原始碼
17
+
18
+ ### KSortEncryption.cs
19
+
20
+ ```csharp
21
+ using System.Text.Json;
22
+
23
+ namespace Furion.DataEncryption;
24
+
25
+ /// <summary>
26
+ /// KSort 加密(資料簽名)
27
+ /// </summary>
28
+ [SuppressSniffer]
29
+ public class KSortEncryption
30
+ {
31
+ private static DateTime _timeStampStartTime = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
32
+
33
+ /// <summary>
34
+ /// 資料加密(簽名)
35
+ /// </summary>
36
+ public static KSortSignature Encrypt(string appId, string appKey, string command, string data, long? timestamp = null)
37
+ {
38
+ ArgumentNullException.ThrowIfNull(appId);
39
+ ArgumentNullException.ThrowIfNull(appKey);
40
+ ArgumentNullException.ThrowIfNull(command);
41
+
42
+ timestamp ??= (long)(DateTime.Now.ToUniversalTime() - _timeStampStartTime).TotalMilliseconds;
43
+
44
+ var dic = new Dictionary<string, object>
45
+ {
46
+ { "app_id", appId },
47
+ { "app_key", appKey },
48
+ { "command", command },
49
+ { "data", data },
50
+ { "timestamp", timestamp },
51
+ };
52
+
53
+ // ksort 排序
54
+ var sortedDic = dic.OrderBy(kvp => kvp.Key);
55
+
56
+ // 半形逗號連接
57
+ var output = string.Join(",", sortedDic.Select(kvp => $"{kvp.Key}={kvp.Value}"));
58
+
59
+ // UTF-8 編碼,32 位小寫 MD5
60
+ var signature = MD5Encryption.Encrypt(output);
61
+
62
+ return new KSortSignature
63
+ {
64
+ app_id = appId,
65
+ app_key = appKey,
66
+ command = command,
67
+ data = data,
68
+ timestamp = timestamp.Value,
69
+ signature = signature
70
+ };
71
+ }
72
+
73
+ /// <summary>
74
+ /// 比較資料簽名(KSortSignature 物件)
75
+ /// </summary>
76
+ public static bool Compare(KSortSignature kSortSignature)
77
+ {
78
+ ArgumentNullException.ThrowIfNull(kSortSignature);
79
+ return Encrypt(kSortSignature.app_id, kSortSignature.app_key,
80
+ kSortSignature.command, kSortSignature.data, kSortSignature.timestamp) == kSortSignature;
81
+ }
82
+
83
+ /// <summary>
84
+ /// 比較資料簽名(JSON 字串)
85
+ /// </summary>
86
+ public static bool Compare(string signatureData)
87
+ {
88
+ var kSortSignature = JsonSerializer.Deserialize<KSortSignature>(signatureData);
89
+ ArgumentNullException.ThrowIfNull(kSortSignature);
90
+ return Encrypt(kSortSignature.app_id, kSortSignature.app_key,
91
+ kSortSignature.command, kSortSignature.data, kSortSignature.timestamp) == kSortSignature;
92
+ }
93
+ }
94
+ ```
95
+
96
+ ### KSortSignature.cs
97
+
98
+ ```csharp
99
+ /// <summary>
100
+ /// KSort 簽名類
101
+ /// </summary>
102
+ public class KSortSignature : IEquatable<KSortSignature>
103
+ {
104
+ public string app_id { get; set; }
105
+ public string app_key { get; set; }
106
+ public string command { get; set; }
107
+ public string data { get; set; }
108
+ public long timestamp { get; set; }
109
+ public string signature { get; set; }
110
+
111
+ public bool Equals(KSortSignature other)
112
+ {
113
+ if (other == null) return false;
114
+ return app_id == other.app_id &&
115
+ app_key == other.app_key &&
116
+ command == other.command &&
117
+ data == other.data &&
118
+ timestamp == other.timestamp &&
119
+ signature == other.signature;
120
+ }
121
+
122
+ public override bool Equals(object obj) => Equals(obj as KSortSignature);
123
+
124
+ public override int GetHashCode()
125
+ => HashCode.Combine(app_id, app_key, command, data, timestamp, signature);
126
+
127
+ public static bool operator ==(KSortSignature lhs, KSortSignature rhs)
128
+ {
129
+ if (ReferenceEquals(lhs, rhs)) return true;
130
+ if (lhs is null || rhs is null) return false;
131
+ return lhs.Equals(rhs);
132
+ }
133
+
134
+ public static bool operator !=(KSortSignature lhs, KSortSignature rhs) => !(lhs == rhs);
135
+ }
136
+ ```
137
+
138
+ ---
139
+
140
+ ## TypeScript 前端原始碼
141
+
142
+ ### data-signature.ts
143
+
144
+ ```typescript
145
+ import CryptoJS from "crypto-js";
146
+
147
+ /**
148
+ * 生成 KSort 簽名
149
+ * @param appId APP_ID
150
+ * @param appKey APP_KEY
151
+ * @param command 命令
152
+ * @param data 資料
153
+ * @param timestamp 時間戳(可選)
154
+ */
155
+ export function signatureByKSort(
156
+ appId: string,
157
+ appKey: string,
158
+ command: string,
159
+ data: any,
160
+ timestamp: number | null = null
161
+ ) {
162
+ // 序列化資料
163
+ const input =
164
+ typeof data === "string" ? (data as string) : JSON.stringify(data);
165
+
166
+ // 生成時間戳
167
+ if (!timestamp) {
168
+ timestamp =
169
+ new Date().getTime() - new Date("1970-01-01T00:00:00Z").getTime();
170
+ }
171
+
172
+ const sData: any = {
173
+ app_id: appId,
174
+ app_key: appKey,
175
+ command,
176
+ data: input,
177
+ timestamp,
178
+ };
179
+
180
+ // 取得所有 key 並排序
181
+ const keys = Object.keys(sData);
182
+ keys.sort();
183
+
184
+ // 生成簽名字串
185
+ let result = "";
186
+ for (let i = 0; i < keys.length; i++) {
187
+ const key = keys[i];
188
+ const value = sData[key];
189
+ result += key + "=" + value;
190
+ if (i < keys.length - 1) {
191
+ result += ",";
192
+ }
193
+ }
194
+
195
+ // MD5 小寫加密
196
+ const hash = CryptoJS.MD5(result);
197
+ const hexString = hash.toString(CryptoJS.enc.Hex);
198
+ const signature = hexString.toLowerCase();
199
+
200
+ return {
201
+ app_id: appId,
202
+ app_key: appKey,
203
+ command,
204
+ data: input,
205
+ timestamp,
206
+ signature,
207
+ };
208
+ }
209
+ ```
210
+
211
+ > 需安裝:`npm install crypto-js`
212
+
213
+ ---
214
+
215
+ ## JavaScript 前端原始碼(ES Module)
216
+
217
+ ### data-signature.js
218
+
219
+ ```javascript
220
+ import CryptoJS from "crypto-js";
221
+
222
+ export function signatureByKSort(appId, appKey, command, data, timestamp = null) {
223
+ const input = typeof data === "string" ? data : JSON.stringify(data);
224
+
225
+ if (!timestamp) {
226
+ timestamp = new Date().getTime() - new Date("1970-01-01T00:00:00Z").getTime();
227
+ }
228
+
229
+ const sData = { app_id: appId, app_key: appKey, command, data: input, timestamp };
230
+
231
+ const keys = Object.keys(sData);
232
+ keys.sort();
233
+
234
+ let result = "";
235
+ for (let i = 0; i < keys.length; i++) {
236
+ result += keys[i] + "=" + sData[keys[i]];
237
+ if (i < keys.length - 1) result += ",";
238
+ }
239
+
240
+ const hash = CryptoJS.MD5(result);
241
+ const signature = hash.toString(CryptoJS.enc.Hex).toLowerCase();
242
+
243
+ return { app_id: appId, app_key: appKey, command, data: input, timestamp, signature };
244
+ }
245
+ ```
246
+
247
+ ---
248
+
249
+ ## 使用範例
250
+
251
+ ### C# 簽名與驗證
252
+
253
+ ```csharp
254
+ // 簽名
255
+ var appId = "ca36cb2858ce3517df772ec34ce92f21";
256
+ var appKey = "95e4a4f651c2d62679c4c150f2e39f4a";
257
+ var command = "add.user";
258
+ var data = JsonSerializer.Serialize(new { id = 1, name = "Furion" });
259
+
260
+ KSortSignature sData = KSortEncryption.Encrypt(appId, appKey, command, data);
261
+
262
+ // 驗證
263
+ bool result = KSortEncryption.Compare(sData); // KSortSignature 物件
264
+ bool result2 = KSortEncryption.Compare(jsonString); // JSON 字串
265
+ ```
266
+
267
+ ### TypeScript 前端簽名
268
+
269
+ ```typescript
270
+ const sData = signatureByKSort(
271
+ "ca36cb2858ce3517df772ec34ce92f21",
272
+ "95e4a4f651c2d62679c4c150f2e39f4a",
273
+ "add.user",
274
+ { id: 1, name: "furion" }
275
+ );
276
+
277
+ fetch("http://furion.net/api/endpoint", {
278
+ method: "POST",
279
+ headers: { "Content-Type": "application/json" },
280
+ body: JSON.stringify(sData),
281
+ }).then(res => res.json()).then(data => {});
282
+ ```
283
+
284
+ ### 後端接收(最佳實踐)
285
+
286
+ ```csharp
287
+ [HttpPost]
288
+ public async Task AddUser([Required] KSortSignature sData)
289
+ {
290
+ // 驗證簽名
291
+ if (!KSortEncryption.Compare(sData))
292
+ throw new Exception("非法資料");
293
+
294
+ // 反序列化取得原始資料
295
+ var data = JsonSerializer.Deserialize<YourDataType>(sData.data);
296
+ }
297
+ ```
298
+
299
+ ---
300
+
301
+ ## 注意事項
302
+
303
+ - `appId` 和 `appKey` 應從伺服器動態取得,**切勿明文寫在客戶端程式碼中**
304
+ - 可實作中介軟體統一驗證簽名
305
+ - 簽名格式固定為:`app_id=xxx,app_key=xxx,command=xxx,data=xxx,timestamp=xxx` → MD5 32 位小寫
@@ -0,0 +1,342 @@
1
+ # 21. 全球化和本地化(多語言)
2
+
3
+ ---
4
+
5
+ ## 21.1 概述
6
+
7
+ 全球化是設計支援不同區域性的應用程式的過程;本地化是將全球化應用調整為特定區域設定的過程。Furion 框架提供了完整的多語言處理服務。
8
+
9
+ ---
10
+
11
+ ## 21.2 註冊服務
12
+
13
+ ```csharp
14
+ public void ConfigureServices(IServiceCollection services)
15
+ {
16
+ services.AddControllersWithViews()
17
+ .AddAppLocalization(); // 註冊多語言
18
+ }
19
+
20
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
21
+ {
22
+ // 必須在 app.UseRouting() 之前
23
+ app.UseAppLocalization();
24
+
25
+ app.UseStaticFiles();
26
+ app.UseRouting();
27
+ }
28
+ ```
29
+
30
+ > ⚠️ `app.UseAppLocalization()` 必須在 `app.UseRouting()` **之前**註冊。
31
+
32
+ ---
33
+
34
+ ## 21.3 基本設定
35
+
36
+ ### 21.3.1 LocalizationSettings 設定
37
+
38
+ ```json
39
+ {
40
+ "LocalizationSettings": {
41
+ "SupportedCultures": ["zh-CN", "en-US"],
42
+ "DefaultCulture": "zh-CN"
43
+ }
44
+ }
45
+ ```
46
+
47
+ ### 21.3.2 建立 Resources 資料夾
48
+
49
+ 在 **Web 啟動專案層**新增 `Resources` 資料夾。
50
+
51
+ ---
52
+
53
+ ## 21.4 L 靜態類
54
+
55
+ | 屬性 / 方法 | 說明 |
56
+ |-------------|------|
57
+ | `L.Text[文字]` | 轉換文字多語言 |
58
+ | `L.Html[HTML, 格式化]` | 轉換 HTML 多語言 |
59
+ | `L.TextOf<T>()[文字]` | 使用完整型別限定名轉換(4.8.6+) |
60
+ | `L.HtmlOf<T>()[HTML, 格式化]` | 使用完整型別限定名轉換(4.8.6+) |
61
+ | `L.SetCulture(區域碼)` | 設定語言(下次請求生效);第二個參數 `true` 立即生效 |
62
+ | `L.GetSelectCulture()` | 取得目前語言區域 |
63
+ | `L.GetCultures()` | 取得系統支援的多語言列表 |
64
+ | `L.SetCurrentUICulture(區域碼)` | 執行階段設定目前執行緒語言,**立即生效**(4.8.3.10+) |
65
+ | `L.GetCurrentUICulture()` | 取得目前執行緒 UI 區域性(4.8.3.10+) |
66
+ | `L.GetString<T>(u => u.屬性)` | 根據表達式取得翻譯(4.8.3.10+) |
67
+ | `L.GetString(name, culture)` | 取得指定區域翻譯(4.8.8.41+) |
68
+ | `L.GetDefaultCulture()` | 取得本地設定預設語言(4.8.8.49+) |
69
+
70
+ > `L.SetCulture` 實際上是寫入客戶端 Cookie,對**下次請求**生效。若需**立即生效**,可用 `L.SetCulture(區域碼, true)` 或搭配 `L.SetCurrentUICulture`。
71
+
72
+ ---
73
+
74
+ ## 21.5 使用範例
75
+
76
+ ### 在類別中使用
77
+
78
+ ```csharp
79
+ var apiInterface = L.Text["API 接口"];
80
+ var sourceCode = L.Text["源碼地址"];
81
+ var other = L.Text["其他{0}", "的"];
82
+ var name = L.Html["<b>Hello</b><i> {0}</i>", name];
83
+ ```
84
+
85
+ ### 在檢視中使用
86
+
87
+ ```razor
88
+ @using Furion.Localization
89
+
90
+ <a href="/api">@L.Text["API 接口"]</a>
91
+ <a href="https://gitee.com/dotnetchina/Furion">@L.Text["源碼地址"]</a>
92
+ ```
93
+
94
+ ### 在驗證特性中使用
95
+
96
+ ```csharp
97
+ [Required(ErrorMessage = "必填訊息")]
98
+ ```
99
+
100
+ > 所有驗證特性已自動支援多語言,無需透過 `L.Text[]` 呼叫。
101
+
102
+ ### 在例外訊息中使用
103
+
104
+ ```csharp
105
+ [ErrorCodeType]
106
+ public enum ErrorCodes
107
+ {
108
+ [ErrorCodeItemMetadata("使用者名稱不能為空")]
109
+ z1000
110
+ }
111
+
112
+ throw Oops.Oh(ErrorCodes.z1000); // 自動套用多語言
113
+ ```
114
+
115
+ ### SharedResource 模式(4.3.7+)
116
+
117
+ 建立 `SharedResource.cs`,定義屬性避免硬編碼字串散落各處:
118
+
119
+ ```csharp
120
+ public class SharedResource
121
+ {
122
+ public string Hello { get; set; }
123
+ public string Name { get; set; }
124
+ }
125
+
126
+ // 使用
127
+ var hello = L.GetString<SharedResource>(u => u.Hello);
128
+ var name = L.GetString<SharedResource>(u => u.Name);
129
+
130
+ // 或使用 nameof
131
+ var hello = L.Text[nameof(SharedResource.Hello)];
132
+ ```
133
+
134
+ ---
135
+
136
+ ## 21.6 建立語言翻譯檔案
137
+
138
+ 在 `Resources` 資料夾中新增 `.resx` 資源檔案,命名規則:`Lang.區域碼.resx`(如 `Lang.en-US.resx`),填入對應語言的鍵值對。
139
+
140
+ > 若未找到對應翻譯,自動顯示原始字串:`L.Text["沒找到"]` → 直接輸出 `"沒找到"`。
141
+
142
+ 自訂資源檔名或存放程式集:
143
+
144
+ ```json
145
+ {
146
+ "LocalizationSettings": {
147
+ "LanguageFilePrefix": "MyLang",
148
+ "AssemblyName": "你的其他層程式集名稱"
149
+ }
150
+ }
151
+ ```
152
+
153
+ ---
154
+
155
+ ## 21.7 切換語言
156
+
157
+ ### 21.7.1 URL 參數方式(優先級最高)
158
+
159
+ ```
160
+ ?culture=en-US
161
+ ```
162
+
163
+ 自訂參數名稱(4.9.3.14+):
164
+
165
+ ```csharp
166
+ app.UseAppLocalization(options =>
167
+ {
168
+ options.AddInitialRequestCultureProvider(new CustomizeQueryStringRequestCultureProvider("my-culture"));
169
+ });
170
+ ```
171
+
172
+ ### 21.7.2 Cookies 方式
173
+
174
+ ```csharp
175
+ L.SetCulture("en-US");
176
+ ```
177
+
178
+ 自訂 Cookie 名稱:
179
+
180
+ ```csharp
181
+ app.UseAppLocalization(options =>
182
+ {
183
+ options.AddInitialRequestCultureProvider(new CookieRequestCultureProvider { CookieName = "custom-cookie-name" });
184
+ });
185
+ ```
186
+
187
+ ### 21.7.3 客戶端瀏覽器語言自動切換
188
+
189
+ 自動根據瀏覽器語言設定配置(推薦)。
190
+
191
+ ### 21.7.4 請求標頭方式
192
+
193
+ ```
194
+ Accept-Language: en-US
195
+ ```
196
+
197
+ ### 21.7.5 Cookie 請求標頭方式
198
+
199
+ ```
200
+ Cookie: c%3Den-US%7Cuic%3Den-US
201
+ ```
202
+
203
+ 明文格式:`c=en-US|uic=en-US`(必須使用 URL 編碼格式,`c` 和 `uic` 值通常一致)。
204
+
205
+ ---
206
+
207
+ ## 21.8 相依性注入方式
208
+
209
+ ```csharp
210
+ public class TestController : Controller
211
+ {
212
+ private readonly IStringLocalizer _localizer;
213
+
214
+ public TestController(IStringLocalizerFactory factory)
215
+ {
216
+ _localizer = factory.Create();
217
+ }
218
+
219
+ public IActionResult About()
220
+ {
221
+ ViewData["Message"] = _localizer["Your application description page."];
222
+ }
223
+ }
224
+ ```
225
+
226
+ ---
227
+
228
+ ## 21.9 LocalizationSettings 完整設定
229
+
230
+ | 屬性 | 型別 | 預設值 | 說明 |
231
+ |------|------|--------|------|
232
+ | `ResourcesPath` | `string` | `"Resources"` | 資源目錄 |
233
+ | `SupportedCultures` | `string[]` | — | 支援的語言區域碼列表 |
234
+ | `DefaultCulture` | `string` | 取 `SupportedCultures` 第一項 | 預設語言區域碼 |
235
+ | `LanguageFilePrefix` | `string` | `"Lang"` | 資源檔案前綴 |
236
+ | `AssemblyName` | `string` | 啟動層名稱 | 資源檔案存放程式集 |
237
+ | `DateTimeFormatCulture` | `string` | — | DateTime 時間格式化區域(4.8.7.31+) |
238
+
239
+ ---
240
+
241
+ ## 21.10 中文無法切換問題
242
+
243
+ 透過 `dotnet build` 編譯發佈後可能無法生成 `zh-CN` 資源檔案。解決方式:將 `Lang.zh-CN.resx` 改為 `Lang.zh-Hans.resx` 或 `Lang.zh-Hant.resx` 或 `Lang.zh.resx`。
244
+
245
+ ---
246
+
247
+ ## 21.11 基於 JSON 檔案多語言(4.8.6+)
248
+
249
+ `.resx` 無法在執行階段修改,推薦使用 `.json` 格式(可動態修改)。
250
+
251
+ ```bash
252
+ dotnet add package My.Extensions.Localization.Json
253
+ ```
254
+
255
+ ```csharp
256
+ public void ConfigureServices(IServiceCollection services)
257
+ {
258
+ services.AddControllersWithViews()
259
+ .AddAppLocalization(settings =>
260
+ {
261
+ services.AddJsonLocalization(options => options.ResourcesPath = [settings.ResourcesPath]);
262
+ });
263
+ }
264
+ ```
265
+
266
+ 在 `Resources` 資料夾中新增 `Lang.區域碼.json`(如 `Lang.en-US.json`),其餘用法與 `.resx` 完全相同。
267
+
268
+ ---
269
+
270
+ ## 21.13 在 WinForm / WPF 中使用
271
+
272
+ ### WinForm
273
+
274
+ ```csharp
275
+ internal static class Program
276
+ {
277
+ [STAThread]
278
+ static void Main()
279
+ {
280
+ Serve.RunNative(services => services.AddAppLocalization());
281
+ ApplicationConfiguration.Initialize();
282
+ Application.Run(new Form1());
283
+ }
284
+ }
285
+
286
+ // 使用
287
+ L.SetCurrentUICulture("en-US");
288
+ label1.Text = L.Text["API 接口"];
289
+ ```
290
+
291
+ ### WPF
292
+
293
+ ```csharp
294
+ public partial class App : Application
295
+ {
296
+ public App()
297
+ {
298
+ Serve.RunNative(services => services.AddAppLocalization());
299
+ }
300
+ }
301
+
302
+ // 使用
303
+ L.SetCurrentUICulture("en-US");
304
+ label1.Name = L.Text["API 接口"];
305
+ ```
306
+
307
+ > 兩者都需添加 `appsettings.json`(設定為「如果較新則複製」)和 `Resources` 資料夾。
308
+
309
+ ---
310
+
311
+ ## 21.14 DateTime.Now 問題(4.8.7.31+)
312
+
313
+ 啟用多語言後 `DateTime.Now` 可能受時區影響。建議使用 `DateTime.UtcNow` 或 `DateTimeOffset.UtcNow`,或固定時間區域:
314
+
315
+ ```json
316
+ {
317
+ "LocalizationSettings": {
318
+ "DateTimeFormatCulture": "zh-CN"
319
+ }
320
+ }
321
+ ```
322
+
323
+ ---
324
+
325
+ ## 21.15 Blazor 專案注意事項
326
+
327
+ `.razor` 元件僅支援 `Accept-Language` 請求標頭和 Cookie 方式切換語言。
328
+
329
+ ---
330
+
331
+ ## 更新日誌摘要
332
+
333
+ | 版本 | 日期 | 內容 |
334
+ |------|------|------|
335
+ | 4.9.7.23 | 2025.03.10 | `L` 靜態屬性返回值改為非空 |
336
+ | 4.9.6.19 | 2024.12.27 | 修復無法自訂 CookieName 問題 |
337
+ | 4.9.3.14 | 2024.05.30 | 多語言支援自訂 Url/Cookie/Header 參數 |
338
+ | 4.8.8.49 | 2023.10.25 | 新增 `L.GetDefaultCulture()` |
339
+ | 4.8.8.41 | 2023.08.04 | 新增 `L.GetString(name, culture)` |
340
+ | 4.8.7.31 | 2023.03.31 | 新增 `DateTimeFormatCulture` 設定 |
341
+ | 4.8.6 | 2023.02.08 | 支援 `.json` 檔案設定方式 |
342
+ | 4.8.3.10 | 2022.12.23 | 新增 `SetCurrentUICulture` / `GetCurrentUICulture` |