在 Linux 環境 publish 專案時無法產出 zh-TW 資源檔
前言
目前執行的專案需要支援多國語系,因此資源檔都以【zh-TW】結尾作為繁體中文的語系名稱,在開發時也沒有任何異狀 (在 Windows 環境),但就在部署後發現中文語系完全失效,接著就是一連串的除錯過程了。
前情提要
多國語系支援【zh-TW】繁體中文 、 【en-US】英文及【ja】日語,因此設定三種語系的資源檔。
在 startup.cs 中設定依照 Request 的 Accept-Language Header 來決定用戶的語系。
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization();
services.Configure<RequestLocalizationOptions>(options =>
{
// 從 Request Header 的 Accept-Language 資訊判斷語系
var mainLang = "zh-TW";
var supportedLangs = new List<string> { "zh-TW", "en-US", "ja" };
var supportedCultures = supportedLangs.Select(l => new CultureInfo(l)).ToList();
options.DefaultRequestCulture = new RequestCulture(culture: mainLang, uiCulture: mainLang);
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(context =>
{
var languages = context.Request.Headers["Accept-Language"].ToString();
var currentLanguage = languages.Split(',').FirstOrDefault();
var isLangNotSupport = string.IsNullOrEmpty(currentLanguage) || !supportedLangs.Contains(currentLanguage);
var defaultLanguage = isLangNotSupport ? mainLang : currentLanguage;
return Task.FromResult(new ProviderCultureResult(defaultLanguage, defaultLanguage));
}));
});
// ... 略 ...
}
注入 IStringLocalizer<SharedResource> sharedLocalizer 後可透過 key 值 welcome 取出對應語系文字。
[ApiController]
[Route("[controller]")]
public class DemoController : ControllerBase
{
private readonly IStringLocalizer<SharedResource> _sharedLocalizer;
public DemoController(IStringLocalizer<SharedResource> sharedLocalizer)
{
_sharedLocalizer = sharedLocalizer;
}
[HttpGet]
public string Get()
{
return _sharedLocalizer["welcome"];
}
}
在繁中環境瀏覽器的首位語系為【zh-TW】繁體中文。
因此可以取得 key 值為 welcome 的繁中語系「歡迎」文字。
部署網站
目前專案是透過 Docker 環境來 build & publish 後產生 Image 進行部署,因此會加入 dockerfile 於專案目錄中,使用它建置部署網站。
- 使用
mcr.microsoft.com/dotnet/sdk:5.0
作為 build & publish 的容器環境。 - 使用
mcr.microsoft.com/dotnet/aspnet:5.0
作為網站運行的容器環境。 - 複製主要的專案檔到建置環境中,並透過
dotnet restore
還原專案內使用的套件。 - 複製所有檔案到容器後,用
dotnet build
及dotnet publish
建置發佈產出部署檔案。 - 最後把部署檔案複製到網站運行環境,並透過
ENTRYPOINT
指定進入點即可。
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY LocalizedWebApp.API/LocalizedWebApp.API.csproj ./LocalizedWebApp.API/
RUN dotnet restore LocalizedWebApp.API/LocalizedWebApp.API.csproj
COPY . .
WORKDIR "/src/LocalizedWebApp.API"
RUN dotnet build -c Release -o /app/build
FROM build AS publish
RUN dotnet publish -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "LocalizedWebApp.API.dll"]
加入 .dockerignore 檔案。
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
依照 dockerfile 建置發佈專案,並且產生 docker image (指定名稱為 localizedapp)
$ docker build -t localizedapp .
成功建置後,確認有名為 localizedapp 的 Image 被產出了
$ docker images
依據剛剛建立的 localizedapp Docker Image 啟動一個新的容器 (名為 mylocalizedapp 的 Container)
$ docker run -e "DOTNET_ENVIRONMENT=Development" -d --name=mylocalizedapp --rm -p 8000:80 localizedapp
- 使用 Development 環境參數來做測試
- 產生名為 mylocalizedapp 的 Container
- 來源取自名為 localizedapp 的 Image 檔
確認一下執行中的容器確實存在,這樣就表示我的站台已經在容器中運行了。
$ docker ps
中文語系失效
訪問 swagger 站台進行測試時,突然發現無法取得繁中語系文字,取而代之的是回傳我們定義的 key 值 welcome 字串。
http://localhost:8000/swagger/index.html
嘗試切換成其他語系如日文又可以正常運作,這是啥情況??
本機建置
滿頭問號地直覺性在本機(windows)直接產出發佈檔案來看看,測試結果如預期地產出【ja】、【zh-TW】及【en-US】三個語系資料夾,透過 IIS 來訪問站台也沒有發生相同的問題,所以猜想是不是在容器環境裡面建置發佈時發生什麼問題了?
進入容器看看
殺進容器看看佈署的資料是什麼,結果發現有【en-US】及【ja】但怎麼就是沒有【zh-TW】資料夾;當時我還在想是不是 COPY 語法哪裡寫得不對,讓【zh-TW】資料夾沒有順利被一起搬進來,完全壓根沒有想到是【zh-TW】根本就沒有被建立,此時又花了一些冤枉時間。
$ docker exec -it <container name> /bin/bash
發現問題
就在一陣明查暗訪 Google 大神的協助,發現原來 linux 環境下根本不認得【zh-TW】啊!所以在建置發佈時【zh-TW】資源檔根本不會產生,也就導致我的中文語系文字都只剩下 key 值的呈現,因為我的【zh-TW】資源檔根本不存在啊!
Resource dll is not generating for China(zh-CN) in Linux when build the app
Cultures aliased by ICU cannot be used for resource localization on non-Windows environments
zh-CN/zh-TW don't use parent cultures of zh-Hans/zh-Hant on Linux
排除問題
既然在 Linux 環境建置時看不懂【zh-TW】,那就只好順著它使用【zh-Hant】作為繁體語系代碼;因此我們的 resource file 名稱要從 SharedResource.zh-TW.resx 改成 SharedResource.zh-Hant.resx 才行。
接著當我們從 Accept-Language Header 收到語系代碼得時候,只要是【zh-TW】就自動轉成【zh-Hant】來使用,這樣就不會有問題產生了(如果是簡體中文可以用 【zh-Hans】)。
services.Configure<RequestLocalizationOptions>(options =>
{
// 從 Request Header 的 Accept-Language 資訊判斷語系
// 專案透過 Linux docker 環境 build & publish 產出建置檔案後,並建立可部屬的 docker image 檔案
// 但在 Linux 建置專案時,系統只認得 zh-Hant 並只能產出 zh-Hant 資源檔 (zh-TW 不產出 [在 windows 環境不會])
// 所以必須使用 zh-Hant 作為中文繁體的語系碼,否則 zh-TW 語系會失效!
var mainLang = "zh-Hant";
var supportedLangs = new List<string> { "zh-TW", "zh-Hant", "en-US", "ja" };
var supportedCultures = supportedLangs.Select(l => new CultureInfo(l)).ToList();
options.DefaultRequestCulture = new RequestCulture(culture: mainLang, uiCulture: mainLang);
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(context =>
{
var languages = context.Request.Headers["Accept-Language"].ToString();
var currentLanguage = languages.Split(',').FirstOrDefault();
currentLanguage = currentLanguage.Equals("zh-TW", StringComparison.OrdinalIgnoreCase) ? mainLang : currentLanguage;
var isLangNotSupport = string.IsNullOrEmpty(currentLanguage) || !supportedLangs.Contains(currentLanguage);
var defaultLanguage = isLangNotSupport ? mainLang : currentLanguage;
return Task.FromResult(new ProviderCultureResult(defaultLanguage, defaultLanguage));
}));
});
重新部署後就可以看到【zh-Hant】語系檔被產出了。
當然文字也可以正常顯示囉!結案!
希望此篇文章可以幫助到需要的人
若內容有誤或有其他建議請不吝留言給筆者喔 !