Giter Site home page Giter Site logo

codingseb / localization Goto Github PK

View Code? Open in Web Editor NEW
35.0 4.0 6.0 399 KB

A suite to localize C# and WPF projects easily based on file format you choose.

License: MIT License

C# 100.00%
localization wpf language translate translation xaml markup localizer localize csharp

localization's Introduction

Localization

A suit of libs to localize C# and WPF projects easily based on file format you choose.

Replace the archived TranslateMe lib

Nuget Version
CodingSeb.Localization NuGet Status
CodingSeb.Localization.JsonFileLoader NuGet Status
CodingSeb.Localization.YamlFileLoader NuGet Status
CodingSeb.Localization.WPF NuGet Status
CodingSebLocalization.Fody NuGet Status

The differents parts of the project

The library is composed of 4 parts :

  1. The part "Core" : Nuget "CodingSeb.Localization" Contains the dictionnary of all translations for a "TextId" in C#.

  2. A part "FileLoader" : Allow to open a type of file that contains translations to load in the dictionnary of the "Core" part.

  3. The part to translate (localize) XAML (WPF) "CodingSeb.Localization.WPF". Provide Tr and MultiTr markups and some converters to use in Bindings. It use the "Core" in backend.

  4. The part to translate ViewModel (INotifyPropertyChanged). Provide Localize attribute to put on ViewModel properties that are localized

Installation

With Nuget

For Simple C# projects

PM> Install-Package CodingSeb.Localization.JsonFileLoader

or

PM> Install-Package CodingSeb.Localization.YamlFileLoader

or

PM> Install-Package CodingSeb.Localization

and implement your own FileLoader.

For WPF projects
Add this :

PM> Install-Package CodingSeb.Localization.WPF

and

PM> Install-Package CodingSebLocalization.Fody

Use it in C# :

To localize a text

using CodingSeb.Localization
/// ...

// To translate a text in the current language
// Loc.Tr("TextId");
Loc.Tr("SayHello");
// To show a default text if the text is not localized in the current language
// Loc.Tr("TextId", "DefaultText")
Loc.Tr("SayHello", "Not localized");
// To Translate a text in fixed language
// Loc.Tr("TextId","DefaultText" "LanguageId");
Loc.Tr("SayHello", null, "fr");

To Change the current language

Loc.Instance.CurrentLanguage = "en";
Loc.Instance.CurrentLanguage = "fr";
Loc.Instance.CurrentLanguage = "es";
// ...
// To get availables languages
Collection<string> languages = Loc.AvailableLanguages;

Use it In XAML (WPF) :

(no xmlns needed Tr Markup is available as soon as CodingSeb.Localization.WPF is in project's references)

Simple localization with the Markup Tr

<TextBlock Text="{Tr 'SayHello'}"/>
<TextBlock Text="{Tr 'SayHello', DefaultText='Not localized'}"/>
<TextBlock Text="{Tr 'SayHello', LanguageId='fr'}"/>

In general use XML escape to escape special characters. For single quote use [apos] to escape. XML escape does'nt work in this case for inline Tr markup. Or use the following format :

<!-- textId can be automatically calculate (with x:Name and the context of the element) -->
<Label x:Name="lbMyLabel" >
  <Label.Content>
    <Tr DefaultText="Text with a ' here" />
  </Label.Content>
</Label>

To Translate with Bindings

<!-- To use the Binding as flexible TextId -->
    <TextBlock Text="{Tr {Binding MyPropertyAsTextId}, DefaultText='Not localized'}"/>
    <!-- or -->
    <TextBlock Text="{Tr TextIdBinding={Binding MyPropertyAsTextId}, DefaultText='Not localized'}"/>
    <!-- or -->
    <TextBlock Text="{Binding MyPropertyAsTextId, Converter={TrTextIdConverter DefaultText='Not localized'}"/>
<!-- With StringFormat of the TextId (for enum for example) -->
    <TextBlock Text="{Tr {Binding MyPropertyAsPartOfTextId}, TextIdStringFormat='MyEnum{0}'}"/>
    <!-- or -->
    <TextBlock Text="{Tr TextIdBinding={Binding MyPropertyAsPartOfTextId}, TextIdStringFormat='MyEnum{0}'}"/>
    <!-- or -->
    <TextBlock Text="{Binding MyPropertyAsPartOfTextId, Converter={TrTextIdConverter TextIdStringFormat='MyEnum{0}'}"/>

<!-- To use the Binding as flexible LanguageId -->
   <TextBlock Text="{Binding MyPropertyAsLanguageId, Converter={TrLanguageIdConverter TextId='SayHello'}" />

<!-- To use the Binding as a value to inject in the localized text -->

    <!-- The translation would be "Hello {0}" or "Bonjour {0}"  -->
    <TextBlock Text="{Tr SayHello, {Binding FirstName}}" />
    <!-- or -->
    <TextBlock Text="{Tr SayHello, StringFormatArgBinding={Binding FirstName}}" />
    <!-- or -->
    <TextBlock Text="{Binding FirstName, Converter={TrStringFormatConverter TextId='SayHello'}" />

<!-- The translation would be "Hello {0} {1}" or "Bonjour {0} {1}"  -->
    <TextBlock Text="{Tr SayHello, {Binding FirstName}, {Binding LastName}}" />
    <!-- or -->
    <TextBlock>
        <TextBlock.Text>
            <Tr TextId="SayHello">
	        <Tr.StringFormatArgsBindings>
                    <Binding Path="FirstName" />
                    <Binding Path="LastName" />
		</Tr.StringFormatArgBinding>
            </Tr>
        </TextBlock.Text>
    </TextBlock>
    <!-- or -->
    <TextBlock>
        <TextBlock.Text>
            <MultiBinding Converter="{TrStringFormatMultiValuesConverter TextId='SayHello'}">
                <Binding Path="FirstName" />
                <Binding Path="LastName" />
            </MultiBinding>
        </TextBlock.Text>
    </TextBlock>

To concatenate some translations

<TextBlock>
    <TextBlock.Text>
        <MultiTr>
            <Tr TextId=TextId1 />
            <Tr TextId=TextId2 />
	    <!-- ... -->
        </MultiTr>
    </TextBlock.Text>
</TextBlock>
<!-- or simpler syntax -->
<TextBlock Text="{MultiTr {Tr TextId1}, {Tr TextId2}}" />
<!-- or even simpler -->
<TextBlock Text="{MultiTr TextId1, TextId2}" />

<!-- To specify the way it concatenate (by default separate by a space) -->
    <TextBlock Text="{MultiTr TextId1, TextId2, Separator=' - '}" />
    <!-- or -->
    <TextBlock Text="{MultiTr TextId1, TextId2, StringFormat='{0}, {1}.'}" />

Remark : By default the translation made in the XAML are automatically updated when current language changed.
In templates (DataTemplate, ControlTemplate) Bindings with TrConverters do not update when language change. Prefer to use Tr Markup with bindings in templates

To Change the current language from the xaml

<!-- to add in the root tag of the xaml file  : 
xmlns:loc="clr-namespace:Localization;assembly=Localization" -->
<ComboBox ItemsSource="{Binding Source={x:Static loc:Loc.AvailableLanguages}}"
          SelectedItem="{Binding CurrentLanguage, Source={x:Static loc:Loc.Instance}}"/>

Use it In ViewModel (Fody) :

You can use the Property attibute Localize to automatically generate the PropertyChanged event for the property when CurrentLanguageChanged.

public class LocalizedWithFodyClass : INotifyPropertyChanged
{
    [Localize]
    public string TestProperty => Loc.Tr("TestLabel");

    [Localize(nameof(TextIdInAttribute))]
    public string TextIdInAttribute { get; set; }

    // ...

    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

The specific code is injected at compile time thanks to Fody. It is compatible with PropertyChanged.Fody or other similar fody addins like ReactiveUI.Fody. Just ensure that CodingSebLocalization is defined after in the FodyWeavers.xml file.

<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <PropertyChanged />
  <!-- or -->
  <ReactiveUI />
  <!-- ... -->
  <CodingSebLocalization />	
</Weavers>

OK But... ...How I define my translations ?

JsonFileLoader

With the default JsonFileLoader, Translations are defined in JSON files with the extension "*.loc.json".

Here an example :

{
  "LanguageName": {
    "en": "English",
    "es": "Español",
    "fr": "Français"
  },
  "[Localization.Examples.MainWindow].lblCurrentLanguage[Label].Content": {
    "en": "Current language",
    "es": "Lenguaje actual",
    "fr": "Langue courrante"
  },
  "[Localization.Examples.MainWindow].lblHelloInCurrentLanguage[Label].Content": {
    "en": "Hello",
    "es": "Hola",
    "fr": "Bonjour"
  },
  "HelloInCurrentLanguage": {
    "en": "Hello in the current language",
    "es": "Hola en la lengua actual",
    "fr": "Bonjour dans la langue actuelle"
  },
  "[Localization.Examples.MainWindow].lblHelloInCurrentLanguage[Label].ToolTip": {
    "en": "In english",
    "es": "En español",
    "fr": "En français"
  }
}

It's also possible to create a hierarchy :

{
    "AppName": {
        "MainMenu": {
          "FileMenuItem": {
            "Header": {
              "en": "_File",
              "fr": "_Fichier"
            },
            "NewMenuItem": {
              "Header": {
                "en": "_New",
                "fr": "_Nouveau"
              }
            },
            "OpenMenuItem": {
              "Header": {
                "en": "_Open",
                "fr": "_Ouvrir"
              }
            },
            "..."
        }
    }
}

To use like this :

Loc.Tr("AppName.MainMenu.FileMenuItem.Header");
Loc.Tr("AppName.MainMenu.FileMenuItem.NewMenuItem.Header");
Loc.Tr("AppName.MainMenu.FileMenuItem.OpenMenuItem.Header");

or like this in XAML :

<Menu>
    <MenuItem Header="{Tr 'AppName.MainMenu.FileMenuItem.Header', DefaultText='_File'}">
        <MenuItem Header="{Tr 'AppName.MainMenu.FileMenuItem.NewMenuItem.Header', DefaultText='_New'}" 
                  Command="ApplicationCommands.New" />
        <MenuItem Header="{Tr 'AppName.MainMenu.FileMenuItem.OpenMenuItem.Header', DefaultText='_Open'}" 
                  Command="ApplicationCommands.Open" />
        <!-- ... -->

And to load these files :

using CodingSeb.Localization.Loaders;
// You need first to add the specific fileLoader 
LocalizationLoader.Instance.FileLanguageLoaders.Add(new JsonFileLoader());

// ...

// And then you can add your localization file
LocalizationLoader.Instance.AddFile(@"PathToTheFile\Example1.loc.json");
// or load directly a directory with multiple "*.loc.json" files.
LocalizationLoader.Instance.AddDirectory(@"PathToTheDirectory");

So you can change the text of your app or translate it in a new language without recompile all your application.

// or you can also load a translation by code (textId, languageId, value)
LocalizationLoader.Instance.AddTranslation("SayHello", "en", "Hello" );
LocalizationLoader.Instance.AddTranslation("SayHello", "es", "Hola" );
LocalizationLoader.Instance.AddTranslation("SayHello", "fr", "Bonjour" );

YamlFileLoader

For Yaml format of localization files "*.loc.yaml" it's working the same way as the Json

Implement your own file format

If you want to support an other format than json or yaml, you can create your custom FileLanguageLoader. Simply create a class that implement the ILocalizationFileLoader interface and add an instance of your class in the LocalizationLoader :

LocalizationLoader.Instance.FileLanguageLoaders.Add(new YouCustomClassImplementingILocalizationFileLoader());

Find Missing Translations

You can activate an option to be notify when a translation is missing.

// with all TextId and LanguageId that are missing when you trying to translate them.
Loc.LogOutMissingTranslations = true;
Loc.MissingTranslationFound += Loc_MissingTranslationFound;

If you want to log it automatically in a json file you can also use the class JsonMissingTranslationsLogger in the "CodingSeb.Localization.JsonFileLoader" package.

JsonMissingTranslationsLogger.EnableLog();

Tr and WPF Styles

The Trmarkup is usable in Styles. but if a Trigger is used the Tr markup only works if used in static mode : <Tr IsDynamic=False .... In dynamic mode the Trmarkup create in backend a Binding and do not allow to be modified by a Datatrigger. To do a localization in a same manner, prefer to use a binding with a TrTextIdConverter in place of a trigger.

localization's People

Contributors

codingseb avatar dependabot[bot] avatar radj307 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

localization's Issues

Is support JSON Array?

To avoid write long text single line, is any support JSON array string like this?

{
  "SomeString": {
    "id": [
      "Lorem Ipsum is simply dummy text of the printing and typesetting industry. ",
      "\nLorem Ipsum has been the industry's standard dummy text ever since the 1500s",
      "\nwhen an unknown printer took a galley of type and scrambled it to make a type specimen book.."
    ],
    "en": [
      "Lorem Ipsum is simply dummy text of the printing and typesetting industry. ",
      "\nLorem Ipsum has been the industry's standard dummy text ever since the 1500s",
      "\nwhen an unknown printer took a galley of type and scrambled it to make a type specimen book.."
    ]
  }
}

I got this exception
image

Show strings in design view

Would be great if you could see the string whilst in the UI design view of WPF. Love the library but have had to resort to modify the XAML whilst the app is running to fine tune layouts.

Use TrTextIdConverter in ControlTemplate not working

Dear Coding Seb,
Thank for useful project.
I have an issue please help. Language not change when select change in language combobox

<ControlTemplate TargetType="ListBoxItem">
    <Grid>
        <Grid x:Name="GridLevel0">
            <TextBlock Text="{Binding ContentName, Converter={TrTextIdConverter DefaultText='DefaultText'}, UpdateSourceTrigger=PropertyChanged}"/>
        </Grid>
    </Grid>
</ControlTemplate>

image

Multiplayer using problem

Thank you for this useful NuGet package.
I have several applications, but some of them use multi-user mode, such as BlazorApp. I noticed that your examples use Singleton in Loc.Instance.
Here is my base class

/// <summary>
/// Base class for localization.
/// </summary>
public class WsLocaleBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    public Loc Locale { get; set; } = Loc.Instance;
    private WsEnumLanguage _lang;
    public WsEnumLanguage Lang { get => _lang; private set { _lang = value; SetLanguage(_lang); } }

    protected WsLocaleBase()
    {
        Lang = WsEnumLanguage.Russian;
    }

    public virtual void SetLanguage(WsEnumLanguage language)
    {
        switch (Lang = language)
        {
            case WsEnumLanguage.Russian:
                Locale.CurrentLanguage = "ru";
                break;
            case WsEnumLanguage.English:
            default:
                Locale.CurrentLanguage = "en";
                break;
        }
    }

    #endregion
}

Here is my test in which you can see the problem

[TestFixture]
public sealed class WsLocalizationTests
{
    [Test]
    public void Create_2_instances_for_multiplayer()
    {
        Assert.DoesNotThrow(() =>
        {
            WsLocalizationLabelPrint wsLocalization1 = new();
            WsLocalizationLabelPrint wsLocalization2 = new();
            TestContext.WriteLine($"wsLocalization1.Locale.CurrentLanguage: {wsLocalization1.Locale.CurrentLanguage}");
            TestContext.WriteLine($"wsLocalization2.Locale.CurrentLanguage: {wsLocalization2.Locale.CurrentLanguage}");

            wsLocalization1.SetLanguage(WsEnumLanguage.English);
            TestContext.WriteLine($"wsLocalization1.Locale.CurrentLanguage: {wsLocalization1.Locale.CurrentLanguage}");
            TestContext.WriteLine($"wsLocalization2.Locale.CurrentLanguage: {wsLocalization2.Locale.CurrentLanguage}");

            Assert.That(wsLocalization1.Lang, Is.EqualTo(WsEnumLanguage.English));
            Assert.That(wsLocalization1.Locale.CurrentLanguage, Is.EqualTo("en"));
            Assert.That(wsLocalization2.Lang, Is.EqualTo(WsEnumLanguage.Russian));
            Assert.That(wsLocalization2.Locale.CurrentLanguage, Is.EqualTo("ru"));
        });
    }
}

Here is the Visual Studio Test output

wsLocalization1.Locale.CurrentLanguage: ru
wsLocalization2.Locale.CurrentLanguage: ru
wsLocalization1.Locale.CurrentLanguage: en
wsLocalization2.Locale.CurrentLanguage: en  // <--- here is the mistake, must be 'ru'

How can I use multiplayer localization mode?

Installing Costura Fody

When installing Costura ie because I want to create a one binary. The project won't compile as it fails to understand Tr in the XAML.

Plural translations

There isn't an example of a plural translation using the Tr and bindings. How can I do this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.