[.Net Core] 在 Linux 環境 publish 專案時無法產出 zh-TW 資源檔

 在 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 builddotnet 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】語系檔被產出了。

 

當然文字也可以正常顯示囉!結案!

 

 


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !