.NET 全球化API 和 ICU 對CultureInfo的影響

.NET 全球化API 和ICU對CultureInfo的影響 之封面

前陣子實作貨幣的數字中文大寫轉換時,發現Microsoft Visual Studio International Feature Pack 2.0中就有內建的數字轉中文大寫,實作後使用上並沒有什麼問題,直到近期Windows更新後發現,在轉換時會出現” The specified format is not supported in this culture”的錯誤訊息,研究後發現似乎與.NET 全球化API 和 ICU 有關。

1.原始程式:

原本使用Microsoft Visual Studio International Feature Pack 2.0內建的” EastAsiaNumericFormatter.FormatWithCulture”功能來做轉換,程式碼如下。

decimal m = 840;
string str = EastAsiaNumericFormatter.FormatWithCulture(“L”, m, null, new CultureInfo(“zh-CHT”));

.NET全球化和ICU 之範例圖

當輸入為840時,輸出的大寫中文字為「捌佰肆拾」。

2.Windows Update後開始出現錯誤訊息:

在某次的Windows Update後,當程式執行到” EastAsiaNumericFormatter.FormatWithCulture”時,便會出現以下錯誤訊息” The specified format is not supported in this culture”。

.NET全球化和ICU 之範例圖

查看了微軟所提供的程式碼。

internal static EastAsiaFormatter Create(CultureInfo culture, string format)

 {
     while (!culture.IsNeutralCulture && culture.Parent != null)
     {
         culture = culture.Parent;
     }

     if (culture.Equals(new CultureInfo(“zh-CHS”)))
     {
         return format switch
         {
             “L” => new CHSStdFmt(),
             “Ln” => new CHSNorFmt(),
             “Lc” => new CHSCurFmt(),
             _ => null,
         };
     }

     if (culture.Equals(new CultureInfo(“zh-CHT”)))
     {
         return format switch
         {
             “L” => new CHTStdFmt(),
             “Ln” => new CHTNorFmt(),
             “Lc” => new CHTCurFmt(),
             _ => null,
         };
     }

     if (culture.Equals(new CultureInfo(“ja”)))
     {
         return format switch
         {
             “L” => new JPNStdFmt(),
             “Ln” => new JPNNorFmt(),
             “Lt” => new JPNTraFmt(),
             _ => null,
         };
     }

     if (culture.Equals(new CultureInfo(“ko”)))
     {
         return format switch
         {
             “L” => new KORStdFmt(),
             “Lc” => new KORCurFmt(),
             _ => null,
         };
     }

     return null;
 }

發現CultureInfo也有包含原先我們使用的(”zh-CHT”),看上去似乎沒什麼問題。

 

再接著查看” culture.IsNeutralCulture”與”culture.Parent”:

範例圖

IsNeutralCulture為false,因此在while判斷時,culture變成了culture.Parent的”zh-Hant”,又下方的if條件並沒有”zh-Hant”,因此被判斷為不支援的culture format。

3. 全球化API Windows 10上使用 ICU 程式庫:

zh-HansChinese (Simplified)neutral
zh-TWChinese (Traditional, Taiwan)specific
zh-CNChinese (Simplified, PRC)specific
zh-HKChinese (Traditional, Hong Kong S.A.R.)specific
zh-SGChinese (Simplified, Singapore)specific
zh-MOChinese (Traditional, Macao S.A.R.)specific
zhChineseneutral
zh-HantChinese (Traditional)neutral
zh-CHSChinese (Simplified) Legacyneutral
zh-CHTChinese (Traditional) Legacyneutral

查看上方微軟所提供的結果,根據微軟提供的CultureInfo.IsNeutralCulture 屬性,zh-CHT的IsNeutralCulture應為true,而在我們的程式中卻為false,經查發現,Windows 10 2019年5月更新和更新版本包含icu.dll作為OS的一部分,而.NET 5和更新版本預設會使用ICU。在Windows上執行時,.NET 5和更新版本會嘗試載入icu.dll,如果找不到或無法載入ICU程式庫,.NET 5和更新版本會回復為以NLS為基礎的實作。

範例圖

查看上方實際本機實作輸出結果,由於載入了ICU程式庫,而ICU程式庫中的Culture已不包含zh-CHT,因此IsNeutralCulture變成false,導致使用FormatWithCulture時無法在程式中if條件上找到對應的項目。

4.解決方式 使用 NLS 而非 ICU:

因ICU程式庫的影響,在Microsoft Visual Studio International Feature Pack 2.0更新前,僅能手動設定回舊版的程式庫,才能繼續使用微軟尚未更新的功能,而官方提供了三種方法讓使用者修改設定,分別為

  1. 在專案檔新增
    <ItemGroup>
    <RuntimeHostConfigurationOption Include=”System.Globalization.UseNls” Value=”true” />
    </ItemGroup>
  2. 在json 檔案新增:
    {
      “runtimeOptions”: {
         “configProperties”: {
           “System.Globalization.UseNls”: true
          }
      }
    }
  3. 將環境變數 DOTNET_SYSTEM_GLOBALIZATION_USENLS的值設定為true或1。

範例圖

修改後可以看到,IsNeutralCulture變回true了。

5.結語:

使用ICU可能會導致某些全球化相關作業的差異,微軟官網也針對此部分了提供了「全球化最佳做法、當地語系化最佳做法、ASP.NET 應用程式的全球化最佳做法」等的建議,如果使用了官網提供的建議後程式依然出現錯誤或無法執行,也可以依照官方提供的設定方式嘗試改回使用NLS而非ICU。

Reference

  1. https://learn.microsoft.com/zh-tw/dotnet/api/system.globalization.cultureinfo.isneutralculture?view=net-6.0
  2. https://learn.microsoft.com/zh-tw/dotnet/core/extensions/globalization-icu
  3. https://learn.microsoft.com/zh-tw/dotnet/core/compatibility/globalization/5.0/icu-globalization-api

發佈留言

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料