Lokalisering i .NET

Lokalisering är processen att översätta ett programs resurser till lokaliserade versioner för varje kultur som programmet stöder. Du bör gå vidare till lokaliseringssteget först när du har slutfört lokaliseringsgranskningssteget för att kontrollera att det globaliserade programmet är redo för lokalisering.

Ett program som är redo för lokalisering är uppdelat i två konceptuella block: ett block som innehåller alla användargränssnittselement och ett block som innehåller körbar kod. Användargränssnittsblocket innehåller endast lokaliserade användargränssnittselement som strängar, felmeddelanden, dialogrutor, menyer, inbäddade objektresurser och så vidare för den neutrala kulturen. Kodblocket innehåller endast den programkod som ska användas av alla kulturer som stöds. Den vanliga språkkörningen stöder en resursmodell för satellitsammansättning som separerar ett programs körbara kod från dess resurser. Mer information om hur du implementerar den här modellen finns i Resurser i .NET.

För varje lokaliserad version av ditt program lägger du till en ny satellitsammansättning som innehåller det lokaliserade användargränssnittsblocket översatt till lämpligt språk för målkulturen. Kodblocket för alla kulturer bör förbli detsamma. Kombinationen av en lokaliserad version av användargränssnittsblocket med kodblocket genererar en lokaliserad version av ditt program.

I den här artikeln får du lära dig hur du använder implementeringarna IStringLocalizer<T> och IStringLocalizerFactory . All exempelkällkod i den här artikeln förlitar sig på NuGet-paketen Microsoft.Extensions.Localization och Microsoft.Extensions.Hosting . Mer information om värdtjänster finns i .NET Generic Host.

Resursfiler

Den primära mekanismen för att isolera lokala strängar är med resursfiler. En resursfil är en XML-fil med filnamnstillägget .resx . Resursfiler översätts före körningen av det förbrukande programmet, med andra ord representerar de översatt innehåll i vila. Ett resursfilnamn innehåller vanligtvis en språkidentifierare och får följande formulär:

<FullTypeName><.Locale>.resx

Där:

  • <FullTypeName> Representerar lokaliserbara resurser för en viss typ.
  • Det valfria <.Locale> representerar språkvarianten för resursfilens innehåll.

Ange nationella inställningar

Språkvarianten bör definiera språket, som minst, men det kan också definiera kulturen (regionalt språk) och till och med landet eller regionen. Dessa segment avgränsas ofta av - tecknet. Med den extra specificiteten i en kultur tillämpas reglerna för "kulturåterställning" där bästa matchningar prioriteras. Språkvarianten bör mappas till en välkänd språktagg. Mer information finns i CultureInfo.Name.

Scenarier för kulturåterställning

Anta att din lokaliserade app stöder olika serbiska språk och har följande resursfiler för dess MessageService:

Fil Regionalt språk Landskod
MessageService.sr-Cyrl-RS.resx (Kyrillisk, Serbien) RS
MessageService.sr-Cyrl.resx Kyrilliska
MessageService.sr-Latn-BA.resx (Latin, Bosnien och Hercegovina) BA
MessageService.sr-Latn-ME.resx (Latinsk, Montenegro) ME
MessageService.sr-Latn-RS.resx (Latin, Serbien) RS
MessageService.sr-Latn.resx Latin
MessageService.sr.resx † latin
MessageService.resx

† Standard regionalt språk för språket.

När din app körs med CultureInfo.CurrentCulture inställningen till en lokaliseringskultur "sr-Cyrl-RS" försöker den matcha filer i följande ordning:

  1. MessageService.sr-Cyrl-RS.resx
  2. MessageService.sr-Cyrl.resx
  3. MessageService.sr.resx
  4. MessageService.resx

Men om din app kördes med CultureInfo.CurrentCulture uppsättningen till en kultur av "sr-Latn-BA" lokalisering försöker lösa filer i följande ordning:

  1. MessageService.sr-Latn-BA.resx
  2. MessageService.sr-Latn.resx
  3. MessageService.sr.resx
  4. MessageService.resx

Regeln "kulturåterställning" ignorerar nationella inställningar när det inte finns några motsvarande matchningar, vilket innebär att resursfil nummer fyra väljs om den inte kan hitta en matchning. Om kulturen var inställd "fr-FR"på skulle lokaliseringen hamna i filen MessageService.resx , vilket kan vara problematiskt. Mer information finns i Återställningsprocessen för resurser.

Resurssökning

Resursfiler löses automatiskt som en del av en sökningsrutin. Om projektfilens namn skiljer sig från projektets rotnamnområde kan sammansättningsnamnet skilja sig åt. Detta kan förhindra att resurssökningen annars lyckas. För att åtgärda det här matchningsfelet RootNamespaceAttribute använder du för att ge en ledtråd till lokaliseringstjänsterna. När den tillhandahålls används den under resurssökningen.

Exempelprojektet heter example.csproj, som skapar en example.dll och example.exe– men Localization.Example namnområdet används. Använd ett assembly nivåattribut för att korrigera det här matchningsfelet:

[assembly: RootNamespace("Localization.Example")]

Registrera lokaliseringstjänster

Om du vill registrera lokaliseringstjänster anropar du någon av tilläggsmetoderna AddLocalization under konfigurationen av tjänster. Detta aktiverar beroendeinmatning (DI) av följande typer:

Konfigurera lokaliseringsalternativ

Överlagringen AddLocalization(IServiceCollection, Action<LocalizationOptions>) accepterar en setupAction parameter av typen Action<LocalizationOptions>. På så sätt kan du konfigurera lokaliseringsalternativ.

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddLocalization(options =>
{
    options.ResourcesPath = "Resources";
});

// Omitted for brevity.

Resursfiler kan finnas var som helst i ett projekt, men det finns vanliga metoder som har visat sig vara framgångsrika. Oftast följs vägen för minst motstånd. Föregående C#-kod:

Detta skulle göra att lokaliseringstjänsterna söker efter resursfiler i katalogen Resurser .

Använda IStringLocalizer<T> och IStringLocalizerFactory

När du har registrerat (och eventuellt konfigurerat) lokaliseringstjänsterna kan du använda följande typer med DI:

Om du vill skapa en meddelandetjänst som kan returnera lokaliserade strängar bör du tänka på följande MessageService:

using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Localization;

namespace Localization.Example;

public sealed class MessageService(IStringLocalizer<MessageService> localizer)
{
    [return: NotNullIfNotNull(nameof(localizer))]
    public string? GetGreetingMessage()
    {
        LocalizedString localizedString = localizer["GreetingMessage"];

        return localizedString;
    }
}

I föregående C#-kod:

  • Ett IStringLocalizer<MessageService> localizer fält deklareras.
  • Den primära konstruktorn definierar en IStringLocalizer<MessageService> parameter och avbildar den som ett localizer argument.
  • Metoden GetGreetingMessage anropar överföringen IStringLocalizer.Item[String]"GreetingMessage" som ett argument.

Har IStringLocalizer även stöd för parametriserade strängresurser, tänk på följande ParameterizedMessageService:

using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Localization;

namespace Localization.Example;

public class ParameterizedMessageService(IStringLocalizerFactory factory)
{
    private readonly IStringLocalizer _localizer =
        factory.Create(typeof(ParameterizedMessageService));

    [return: NotNullIfNotNull(nameof(_localizer))]
    public string? GetFormattedMessage(DateTime dateTime, double dinnerPrice)
    {
        LocalizedString localizedString = _localizer["DinnerPriceFormat", dateTime, dinnerPrice];

        return localizedString;
    }
}

I föregående C#-kod:

  • Ett IStringLocalizer _localizer fält deklareras.
  • Den primära konstruktorn tar en IStringLocalizerFactory parameter som används för att skapa en IStringLocalizer från ParameterizedMessageService typen och tilldelar den till fältet _localizer .
  • Metoden GetFormattedMessage anropar IStringLocalizer.Item[String, Object[]], skickar "DinnerPriceFormat", ett dateTime objekt och dinnerPrice som argument.

Viktigt!

Det IStringLocalizerFactory krävs inte. I stället är det bättre att använda tjänster för att kräva IStringLocalizer<T>.

Båda IStringLocalizer.Item[] indexerarna returnerar en LocalizedString, som har implicita konverteringar till string?.

Färdigställa allt

Om du vill exemplifiera en app med både meddelandetjänster och lokaliserings- och resursfiler bör du överväga följande Program.cs fil:

using System.Globalization;
using Localization.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using static System.Console;
using static System.Text.Encoding;

[assembly: RootNamespace("Localization.Example")]

OutputEncoding = Unicode;

if (args is [var cultureName])
{
    CultureInfo.CurrentCulture =
        CultureInfo.CurrentUICulture =
            CultureInfo.GetCultureInfo(cultureName);
}

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddLocalization();
builder.Services.AddTransient<MessageService>();
builder.Services.AddTransient<ParameterizedMessageService>();
builder.Logging.SetMinimumLevel(LogLevel.Warning);

using IHost host = builder.Build();

IServiceProvider services = host.Services;

ILogger logger =
    services.GetRequiredService<ILoggerFactory>()
        .CreateLogger("Localization.Example");

MessageService messageService =
    services.GetRequiredService<MessageService>();
logger.LogWarning(
    "{Msg}",
    messageService.GetGreetingMessage());

ParameterizedMessageService parameterizedMessageService =
    services.GetRequiredService<ParameterizedMessageService>();
logger.LogWarning(
    "{Msg}",
    parameterizedMessageService.GetFormattedMessage(
        DateTime.Today.AddDays(-3), 37.63));

await host.RunAsync();

I föregående C#-kod:

Var och en av klasserna *MessageService definierar en uppsättning .resx-filer , var och en med en enda post. Här är exempelinnehållet för resursfilerna MessageService , som börjar med MessageService.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="GreetingMessage" xml:space="preserve">
    <value>Hi friends, the ".NET" developer community is excited to see you here!</value>
  </data>
</root>

MessageService.sr-Cyrl-RS.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="GreetingMessage" xml:space="preserve">
    <value>Здраво пријатељи, ".NЕТ" девелопер заједница је узбуђена што вас види овде!</value>
  </data>
</root>

MessageService.sr-Latn.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="GreetingMessage" xml:space="preserve">
    <value>Zdravo prijatelji, ".NET" developer zajednica je uzbuđena što vas vidi ovde!</value>
  </data>
</root>

Här är exempelinnehållet för resursfilerna ParameterizedMessageService , som börjar med ParameterizedMessageService.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="DinnerPriceFormat" xml:space="preserve">
    <value>On {0:D} my dinner cost {1:C}.</value>
  </data>
</root>

ParameterizedMessageService.sr-Cyrl-RS.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="DinnerPriceFormat" xml:space="preserve">
    <value>У {0:D} моја вечера је коштала {1:C}.</value>
  </data>
</root>

ParameterizedMessageService.sr-Latn.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="DinnerPriceFormat" xml:space="preserve">
    <value>U {0:D} moja večera je koštala {1:C}.</value>
  </data>
</root>

Dricks

Alla XML-kommentarer, scheman och <resheader> element i resursfilen utelämnas avsiktligt för korthet.

Exempelkörningar

Följande exempelkörningar visar de olika lokaliserade utdata, givet målspråk.

Överväg "sr-Latn":

dotnet run --project .\example\example.csproj sr-Latn

warn: Localization.Example[0]
      Zdravo prijatelji, ".NET" developer zajednica je uzbuđena što vas vidi ovde!
warn: Localization.Example[0]
      U utorak, 03. avgust 2021. moja večera je koštala 37,63 ¤.

När du utelämnar ett argument till .NET CLI för att köra projektet används standardsystemkulturen – i det här fallet "en-US":

dotnet run --project .\example\example.csproj

warn: Localization.Example[0]
      Hi friends, the ".NET" developer community is excited to see you here!
warn: Localization.Example[0]
      On Tuesday, August 3, 2021 my dinner cost $37.63.

När du skickar "sr-Cryl-RS"hittas rätt motsvarande resursfiler och lokaliseringen tillämpas:

dotnet run --project .\example\example.csproj sr-Cryl-RS

warn: Localization.Example[0]
      Здраво пријатељи, ".NЕТ" девелопер заједница је узбуђена што вас види овде!
warn: Localization.Example[0]
      У уторак, 03. август 2021. моја вечера је коштала 38 RSD.

Exempelprogrammet tillhandahåller inte resursfiler för "fr-CA", men när det anropas med den kulturen används de icke-lokaliserade resursfilerna.

Varning

Eftersom kulturen hittas men rätt resursfiler inte är det, får du delvis lokalisering när formateringen används:

dotnet run --project .\example\example.csproj fr-CA

warn: Localization.Example[0]
     Hi friends, the ".NET" developer community is excited to see you here!
warn: Localization.Example[0]
     On mardi 3 août 2021 my dinner cost 37,63 $.

Se även