├── .gitattributes ├── .gitignore ├── CS.Localization.slnx ├── CodingSeb.Localization.AssemblyToProcess ├── CodingSeb.Localization.AssemblyToProcess.csproj ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── LocalizedWithFodyAndCustomLocFieldClass.cs ├── LocalizedWithFodyAndCustomLocPropertyClass.cs ├── LocalizedWithFodyClass.cs └── NotifyPropertyChangedBase.cs ├── CodingSeb.Localization.FodyAddin.Fody ├── CodingSeb.Localization.FodyAddin.Fody.csproj ├── Extensions.cs └── ModuleWeaver.cs ├── CodingSeb.Localization.FodyAddin.Tests ├── CodingSeb.Localization.FodyAddin.Tests.csproj └── WeaverTests.cs ├── CodingSeb.Localization.FodyAddin.WantedResult ├── CodingSeb.Localization.FodyAddin.WantedResult.csproj ├── DirectResult.cs └── GlobalSuppressions.cs ├── CodingSeb.Localization.FodyAddin ├── CodingSeb.Localization.FodyAddin.csproj ├── GlobalSuppressions.cs ├── LocFieldAttribute.cs ├── LocPropertyAttribute.cs ├── LocalizeAttribute.cs ├── PropertyChangedTriggerMethodNameForLocalization.cs └── key.snk ├── LICENSE.md ├── Localization.Avalonia ├── Converters │ ├── TrLanguageIdConverter.cs │ ├── TrStringFormatConverter.cs │ ├── TrStringFormatMultiValuesConverter.cs │ └── TrTextIdConverter.cs ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── Localization.Avalonia.csproj ├── MultiTr.cs ├── Properties │ └── AssemblyInfos.cs ├── Tr.cs ├── TrData.cs ├── TrViewModel.cs └── Utils │ └── WeakDictionary.cs ├── Localization.AvaloniaExample ├── .gitignore ├── App.axaml ├── App.axaml.cs ├── Assets │ └── avalonia-logo.ico ├── Converters │ └── BoolToStringConverter.cs ├── FodyWeavers.xml ├── Localization.AvaloniaExample.csproj ├── Program.cs ├── Utils │ └── Languages.cs ├── ViewLocator.cs ├── ViewModels │ ├── ItemViewModel.cs │ ├── MainWindowViewModel.cs │ └── ViewModelBase.cs ├── Views │ ├── MainWindow.axaml │ └── MainWindow.axaml.cs └── lang │ └── Example1.loc.json ├── Localization.Examples ├── App.xaml ├── App.xaml.cs ├── AttachTest.cs ├── Converters │ ├── BoolToStringConverter.cs │ └── StringToIntConverter.cs ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── Helpers │ └── NotifyPropertyChangedBaseClass.cs ├── Localization.Examples.csproj ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Utils │ └── Languages.cs ├── ViewModel │ ├── ItemViewModel.cs │ └── MainViewModel.cs └── lang │ └── Example1.loc.json ├── Localization.JsonFileLoader ├── GlobalSuppressions.cs ├── JsonFileLoader.cs ├── JsonFileLoaderLangIdDecoding.cs ├── JsonMissingTranslationsLogger.cs └── Localization.JsonFileLoader.csproj ├── Localization.Tests ├── FormatterTest.cs ├── InMemoryTranslator.cs ├── Localization.Tests.csproj ├── LocalizationTests.cs └── Resources │ ├── LocFileTest.loc.json │ ├── MissingTranslationsfileForComparaison.json │ └── StructuredTrans.loc.json ├── Localization.WPF.Tests ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── Localization.WPF.Tests.csproj ├── LocalizationWPFTests.cs ├── Models │ ├── Animal.cs │ ├── Notification.cs │ ├── NotifyPropertyChangedBaseClass.cs │ ├── Person.cs │ └── User.cs └── Utils │ └── TestsServiceProvider.cs ├── Localization.WPF ├── AddWPFXAMLStandardNameSpace.cs ├── Converters │ ├── TrConverterBase.cs │ ├── TrLanguageIdConverter.cs │ ├── TrStringFormatConverter.cs │ ├── TrStringFormatMultiValuesConverter.cs │ └── TrTextIdConverter.cs ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── GlobalSuppressions.cs ├── Helpers │ └── LogicalOrVisualTreeHelper.cs ├── LocBinding.cs ├── Localization.WPF.csproj ├── MultiTr.cs ├── Tr.cs ├── TrData.cs ├── TrEnumAsItemSource.cs └── TrViewModel.cs ├── Localization.YamlFileLoader.Tests ├── Localization.YamlFileLoader.Tests.csproj ├── YamlFileLoaderTests.cs └── lang │ ├── MissingTranslationsFileForComparaison.yaml │ └── SomeTexts.loc.yaml ├── Localization.YamlFileLoader ├── Localization.YamlFileLoader.csproj ├── YamlFileLoader.cs ├── YamlFileLoaderLangIdDecoding.cs └── YamlMissingTranslationsLogger.cs ├── Localization ├── EventsArgs │ ├── CurrentLanguageChangedEventArgs.cs │ └── CurrentLanguageChangingEventArgs.cs ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── Formatters │ ├── IFormatter.cs │ ├── InjectionFormatter.cs │ ├── PluralizationFormatter.cs │ └── TernaryFormatter.cs ├── Loaders │ ├── ILocalizationFileLoader.cs │ └── LocalizationLoader.cs ├── Loc.cs ├── LocTranslation.cs ├── Localization.csproj ├── LocalizationMissingTranslationEventArgs.cs └── Translators │ ├── FilesDictionaryTranslator.cs │ └── ITranslator.cs ├── README.md ├── SmokeTest ├── FodyWeavers.xml ├── FodyWeavers.xsd └── SmokeTest.csproj └── WeaverDependencies ├── System.ObjectModel.dll └── System.Runtime.dll /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /CS.Localization.slnx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /CodingSeb.Localization.AssemblyToProcess/CodingSeb.Localization.AssemblyToProcess.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1;net47 5 | true 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /CodingSeb.Localization.AssemblyToProcess/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /CodingSeb.Localization.AssemblyToProcess/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 10 | 11 | 12 | 13 | 14 | A comma-separated list of error codes that can be safely ignored in assembly verification. 15 | 16 | 17 | 18 | 19 | 'false' to turn off automatic generation of the XML Schema file. 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /CodingSeb.Localization.AssemblyToProcess/LocalizedWithFodyAndCustomLocFieldClass.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Windows; 3 | 4 | namespace CodingSeb.Localization.AssemblyToProcess 5 | { 6 | [LocField(nameof(customLoc))] 7 | public class LocalizedWithFodyAndCustomLocFieldClass : NotifyPropertyChangedBase 8 | { 9 | [Localize] 10 | public string TestProperty => customLoc.Translate("TestLabel"); 11 | 12 | [Localize(nameof(TextIdInAttribute))] 13 | public string TextIdInAttribute { get; set; } 14 | 15 | private Loc customLoc = new Loc(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CodingSeb.Localization.AssemblyToProcess/LocalizedWithFodyAndCustomLocPropertyClass.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Windows; 3 | 4 | namespace CodingSeb.Localization.AssemblyToProcess 5 | { 6 | [LocPropertyAttribute(nameof(CustomLoc))] 7 | public class LocalizedWithFodyAndCustomLocPropertyClass : NotifyPropertyChangedBase 8 | { 9 | [Localize] 10 | public string TestProperty => CustomLoc.Translate("TestLabel"); 11 | 12 | [Localize(nameof(TextIdInAttribute))] 13 | public string TextIdInAttribute { get; set; } 14 | 15 | public Loc CustomLoc { get; set; } = new Loc(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CodingSeb.Localization.AssemblyToProcess/LocalizedWithFodyClass.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Windows; 3 | 4 | namespace CodingSeb.Localization.AssemblyToProcess 5 | { 6 | public class LocalizedWithFodyClass : NotifyPropertyChangedBase 7 | { 8 | [Localize] 9 | public string TestProperty => Loc.Tr("TestLabel"); 10 | 11 | [Localize(nameof(TextIdInAttribute))] 12 | public string TextIdInAttribute { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CodingSeb.Localization.AssemblyToProcess/NotifyPropertyChangedBase.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace CodingSeb.Localization.AssemblyToProcess 5 | { 6 | public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged 7 | { 8 | public event PropertyChangedEventHandler PropertyChanged; 9 | 10 | public virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "") 11 | { 12 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CodingSeb.Localization.FodyAddin.Fody/CodingSeb.Localization.FodyAddin.Fody.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | CodingSebLocalization.Fody 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /CodingSeb.Localization.FodyAddin.Fody/Extensions.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using Mono.Cecil.Rocks; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace CodingSeb.Localization.FodyAddin.Fody 8 | { 9 | internal static class Extensions 10 | { 11 | private static readonly List propertyChangedTriggerMethodCommonNames = new List() 12 | { 13 | "NotifyPropertyChanged", 14 | "NotifyPropertyChange", 15 | "OnPropertyChanged", 16 | "OnPropertyChange", 17 | "RaisePropertyChanged", 18 | "RaisePropertyChange", 19 | "NotifyOfPropertyChange", 20 | "NotifyOfPropertyChanged", 21 | "TriggerPropertyChange", 22 | "TriggerPropertyChanged", 23 | "PropertyChangeTrigger", 24 | "PropertyChangedTrigger", 25 | "FirePropertyChange", 26 | "FirePropertyChanged", 27 | }; 28 | 29 | public static bool IsINotifyPropertyChanged(this TypeDefinition typeDefinition) 30 | { 31 | if (typeDefinition?.FullName.Equals("System.Object") != false) 32 | return false; 33 | else if (typeDefinition.Interfaces.Any(@interface => @interface.InterfaceType.FullName.Equals("System.ComponentModel.INotifyPropertyChanged"))) 34 | return true; 35 | else if (typeDefinition.BaseType is TypeDefinition parentTypeDefinition) 36 | return parentTypeDefinition.IsINotifyPropertyChanged(); 37 | else 38 | return false; 39 | } 40 | 41 | public static MethodDefinition FindPropertyChangedTriggerMethod(this TypeDefinition typeDefinition) 42 | { 43 | bool removeFirstEntry = false; 44 | 45 | var attribute = typeDefinition.CustomAttributes.FirstOrDefault(a => a.AttributeType.Name.Equals("PropertyChangedTriggerMethodNameForLocalization")); 46 | 47 | if (attribute != null) 48 | { 49 | propertyChangedTriggerMethodCommonNames.Insert(0, attribute.ConstructorArguments[0].Value.ToString()); 50 | 51 | removeFirstEntry = true; 52 | } 53 | 54 | try 55 | { 56 | if (typeDefinition?.FullName.Equals("System.Object") != false) 57 | return null; 58 | else if (typeDefinition.Methods.FirstOrDefault(m => propertyChangedTriggerMethodCommonNames.Any(name => name.Equals(m.Name, StringComparison.OrdinalIgnoreCase))) is MethodDefinition method) 59 | return method; 60 | else if (typeDefinition.BaseType is TypeDefinition parentTypeDefinition) 61 | return parentTypeDefinition.FindPropertyChangedTriggerMethod(); 62 | else 63 | return null; 64 | } 65 | finally 66 | { 67 | if (removeFirstEntry) 68 | { 69 | propertyChangedTriggerMethodCommonNames.RemoveAt(0); 70 | } 71 | } 72 | } 73 | 74 | public static MethodDefinition FindNearestFinalizeParentMethod(this TypeDefinition typeDefinition) 75 | { 76 | return typeDefinition.Methods.FirstOrDefault(m => m.Name.Equals("Finalize")) 77 | ?? typeDefinition.BaseType.Resolve().FindNearestFinalizeParentMethod(); 78 | } 79 | 80 | public static bool HasLocalizeAttribute(this PropertyDefinition propertyDefinition) 81 | { 82 | return propertyDefinition.CustomAttributes.Any(attribute => attribute.AttributeType.Name.Equals("LocalizeAttribute")); 83 | } 84 | 85 | public static MethodReference MakeHostInstanceGeneric(this MethodReference self, params TypeReference[] args) 86 | { 87 | var reference = new MethodReference( 88 | self.Name, 89 | self.ReturnType, 90 | self.DeclaringType.MakeGenericInstanceType(args)) 91 | { 92 | HasThis = self.HasThis, 93 | ExplicitThis = self.ExplicitThis, 94 | CallingConvention = self.CallingConvention 95 | }; 96 | 97 | foreach (var parameter in self.Parameters) 98 | { 99 | reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); 100 | } 101 | 102 | foreach (var genericParam in self.GenericParameters) 103 | { 104 | reference.GenericParameters.Add(new GenericParameter(genericParam.Name, reference)); 105 | } 106 | 107 | return reference; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /CodingSeb.Localization.FodyAddin.Tests/CodingSeb.Localization.FodyAddin.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | true 6 | 7 | 8 | 9 | 10 | 11 | runtime; build; native; contentfiles; analyzers; buildtransitive 12 | all 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /CodingSeb.Localization.FodyAddin.Tests/WeaverTests.cs: -------------------------------------------------------------------------------- 1 | using CodingSeb.Localization.FodyAddin.Fody; 2 | using Fody; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Reflection; 7 | using Xunit; 8 | 9 | namespace CodingSeb.Localization.FodyAddin.Tests 10 | { 11 | public class WeaverTests 12 | { 13 | private static readonly TestResult testResult; 14 | 15 | static WeaverTests() 16 | { 17 | var weavingTask = new ModuleWeaver(); 18 | testResult = weavingTask.ExecuteTestRun("CodingSeb.Localization.AssemblyToProcess.dll", runPeVerify: false); 19 | } 20 | 21 | [Fact] 22 | public void ValidateThatPropertyWithLocalizeAttributeIsUpdateWhenLanguageChanged() 23 | { 24 | var type = testResult.Assembly.GetType("CodingSeb.Localization.AssemblyToProcess.LocalizedWithFodyClass"); 25 | 26 | FieldInfo propertyNamesField = type.GetField("__localizedPropertyNames__", BindingFlags.NonPublic | BindingFlags.Static); 27 | MethodInfo languageChangedMethod = type.GetMethod("__CurrentLanguageChanged__", BindingFlags.NonPublic | BindingFlags.Instance); 28 | 29 | Assert.NotNull(propertyNamesField); 30 | Assert.NotNull(languageChangedMethod); 31 | 32 | var instance = (dynamic)Activator.CreateInstance(type, true); 33 | 34 | List listOfPropertyNames = propertyNamesField.GetValue(instance) as List; 35 | 36 | Assert.NotNull(listOfPropertyNames); 37 | Assert.Contains("TestProperty", listOfPropertyNames); 38 | 39 | INotifyPropertyChanged notifyPropertyChanged = instance as INotifyPropertyChanged; 40 | 41 | List propertyNames = new List(); 42 | 43 | void NotifyPropertyChanged_PropertyChanged(object sender, PropertyChangedEventArgs e) 44 | { 45 | propertyNames.Add(e.PropertyName); 46 | } 47 | 48 | notifyPropertyChanged.PropertyChanged += NotifyPropertyChanged_PropertyChanged; 49 | 50 | languageChangedMethod.Invoke(instance, new object[] { Loc.Instance, new CurrentLanguageChangedEventArgs("en", "fr") }); 51 | 52 | Assert.Contains("TestProperty", propertyNames); 53 | Assert.Contains("TextIdInAttribute", propertyNames); 54 | 55 | propertyNames.Clear(); 56 | 57 | Assert.Empty(propertyNames); 58 | 59 | Loc.Instance.CurrentLanguage = "es"; 60 | 61 | Assert.Contains("TestProperty", propertyNames); 62 | Assert.Contains("TextIdInAttribute", propertyNames); 63 | 64 | notifyPropertyChanged.PropertyChanged -= NotifyPropertyChanged_PropertyChanged; 65 | } 66 | 67 | [Fact] 68 | public void ValidateCustomInstanceLocPropertyAttribute() 69 | { 70 | var type = testResult.Assembly.GetType("CodingSeb.Localization.AssemblyToProcess.LocalizedWithFodyAndCustomLocPropertyClass"); 71 | 72 | FieldInfo propertyNamesField = type.GetField("__localizedPropertyNames__", BindingFlags.NonPublic | BindingFlags.Static); 73 | MethodInfo languageChangedMethod = type.GetMethod("__CurrentLanguageChanged__", BindingFlags.NonPublic | BindingFlags.Instance); 74 | 75 | Assert.NotNull(propertyNamesField); 76 | Assert.NotNull(languageChangedMethod); 77 | 78 | var instance = (dynamic)Activator.CreateInstance(type, true); 79 | 80 | List listOfPropertyNames = propertyNamesField.GetValue(instance) as List; 81 | 82 | Assert.NotNull(listOfPropertyNames); 83 | Assert.Contains("TestProperty", listOfPropertyNames); 84 | 85 | INotifyPropertyChanged notifyPropertyChanged = instance as INotifyPropertyChanged; 86 | 87 | List propertyNames = new List(); 88 | 89 | void NotifyPropertyChanged_PropertyChanged(object sender, PropertyChangedEventArgs e) 90 | { 91 | propertyNames.Add(e.PropertyName); 92 | } 93 | 94 | notifyPropertyChanged.PropertyChanged += NotifyPropertyChanged_PropertyChanged; 95 | 96 | Loc customLoc = type.GetProperty("CustomLoc").GetValue(instance) as Loc; 97 | 98 | Assert.NotNull(customLoc); 99 | 100 | languageChangedMethod.Invoke(instance, new object[] { customLoc, new CurrentLanguageChangedEventArgs("en", "fr") }); 101 | 102 | Assert.Contains("TestProperty", propertyNames); 103 | Assert.Contains("TextIdInAttribute", propertyNames); 104 | 105 | propertyNames.Clear(); 106 | 107 | Assert.Empty(propertyNames); 108 | 109 | customLoc.CurrentLanguage = "es"; 110 | 111 | Assert.Contains("TestProperty", propertyNames); 112 | Assert.Contains("TextIdInAttribute", propertyNames); 113 | 114 | notifyPropertyChanged.PropertyChanged -= NotifyPropertyChanged_PropertyChanged; 115 | } 116 | 117 | [Fact] 118 | public void ValidateCustomInstanceLocFieldAttribute() 119 | { 120 | var type = testResult.Assembly.GetType("CodingSeb.Localization.AssemblyToProcess.LocalizedWithFodyAndCustomLocFieldClass"); 121 | 122 | FieldInfo propertyNamesField = type.GetField("__localizedPropertyNames__", BindingFlags.NonPublic | BindingFlags.Static); 123 | MethodInfo languageChangedMethod = type.GetMethod("__CurrentLanguageChanged__", BindingFlags.NonPublic | BindingFlags.Instance); 124 | 125 | Assert.NotNull(propertyNamesField); 126 | Assert.NotNull(languageChangedMethod); 127 | 128 | var instance = (dynamic)Activator.CreateInstance(type, true); 129 | 130 | List listOfPropertyNames = propertyNamesField.GetValue(instance) as List; 131 | 132 | Assert.NotNull(listOfPropertyNames); 133 | Assert.Contains("TestProperty", listOfPropertyNames); 134 | 135 | INotifyPropertyChanged notifyPropertyChanged = instance as INotifyPropertyChanged; 136 | 137 | List propertyNames = new List(); 138 | 139 | void NotifyPropertyChanged_PropertyChanged(object sender, PropertyChangedEventArgs e) 140 | { 141 | propertyNames.Add(e.PropertyName); 142 | } 143 | 144 | notifyPropertyChanged.PropertyChanged += NotifyPropertyChanged_PropertyChanged; 145 | 146 | Loc customLoc = type.GetField("customLoc", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(instance) as Loc; 147 | 148 | Assert.NotNull(customLoc); 149 | 150 | languageChangedMethod.Invoke(instance, new object[] { customLoc, new CurrentLanguageChangedEventArgs("en", "fr") }); 151 | 152 | Assert.Contains("TestProperty", propertyNames); 153 | Assert.Contains("TextIdInAttribute", propertyNames); 154 | 155 | propertyNames.Clear(); 156 | 157 | Assert.Empty(propertyNames); 158 | 159 | customLoc.CurrentLanguage = "es"; 160 | 161 | Assert.Contains("TestProperty", propertyNames); 162 | Assert.Contains("TextIdInAttribute", propertyNames); 163 | 164 | notifyPropertyChanged.PropertyChanged -= NotifyPropertyChanged_PropertyChanged; 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /CodingSeb.Localization.FodyAddin.WantedResult/CodingSeb.Localization.FodyAddin.WantedResult.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7\WindowsBase.dll 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /CodingSeb.Localization.FodyAddin.WantedResult/DirectResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Runtime.CompilerServices; 5 | using System.Windows; 6 | 7 | namespace CodingSeb.Localization.FodyAddin.WantedResult 8 | { 9 | public class DirectResult : INotifyPropertyChanged 10 | { 11 | private static readonly List __localizedPropertyNames__ = new List() 12 | { 13 | "TestProperty", 14 | }; 15 | 16 | public DirectResult() 17 | { 18 | WeakEventManager.AddHandler(Loc.Instance, nameof(Loc.Instance.CurrentLanguageChanged), __CurrentLanguageChanged__); 19 | } 20 | 21 | ~DirectResult() 22 | { 23 | WeakEventManager.RemoveHandler(Loc.Instance, nameof(Loc.Instance.CurrentLanguageChanged), __CurrentLanguageChanged__); 24 | } 25 | 26 | protected void __CurrentLanguageChanged__(object sender, CurrentLanguageChangedEventArgs e) 27 | { 28 | __localizedPropertyNames__.ForEach(__NotifyPropertyChanged__); 29 | } 30 | 31 | private void __NotifyPropertyChanged__(string propertyName) 32 | { 33 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 34 | } 35 | 36 | public event PropertyChangedEventHandler PropertyChanged; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /CodingSeb.Localization.FodyAddin.WantedResult/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "", Scope = "namespaceanddescendants", Target = "~N:CodingSeb.Localization.FodyAddin.WantedResult")] 9 | -------------------------------------------------------------------------------- /CodingSeb.Localization.FodyAddin/CodingSeb.Localization.FodyAddin.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | CodingSeb 6 | Copyright © Coding Seb 2020 7 | Inject the code that generate PropertyChanged event on property that has the attribute 'Localize' when the Language of the Localization changed 8 | 9 | 10 | Localization, Fody 11 | $(SolutionDir)/FodyNuget 12 | https://github.com/codingseb/Localization 13 | https://github.com/codingseb/Localization 14 | CodingSebLocalization.Fody 15 | true 16 | true 17 | key.snk 18 | CodingSeb.Localization.FodyAddin 19 | CodingSeb.Localization 20 | LICENSE.md 21 | README.md 22 | 1.4.0.0 23 | 1.4.0.0 24 | 25 | * Breaking change: To support custom Loc instance 26 | 27 | 1.4.0 28 | True 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | True 40 | \ 41 | 42 | 43 | True 44 | \ 45 | 46 | 47 | true 48 | weaver 49 | true 50 | Content 51 | 52 | 53 | true 54 | weaver 55 | true 56 | Content 57 | 58 | 59 | -------------------------------------------------------------------------------- /CodingSeb.Localization.FodyAddin/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Redundancy", "RCS1163:Unused parameter.", Justification = "", Scope = "member", Target = "~M:CodingSeb.Localization.LocalizeAttribute.#ctor(System.String)")] 9 | [assembly: SuppressMessage("Redundancy", "RCS1163:Unused parameter.", Justification = "", Scope = "member", Target = "~M:CodingSeb.Localization.PropertyChangedTriggerMethodNameForLocalization.#ctor(System.String)")] 10 | [assembly: SuppressMessage("Redundancy", "RCS1163:Unused parameter.", Justification = "", Scope = "member", Target = "~M:CodingSeb.Localization.LocPropertyAttribute.#ctor(System.String)")] 11 | [assembly: SuppressMessage("Redundancy", "RCS1163:Unused parameter.", Justification = "", Scope = "member", Target = "~M:CodingSeb.Localization.LocField.#ctor(System.String)")] 12 | [assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "", Scope = "member", Target = "~M:CodingSeb.Localization.PropertyChangedTriggerMethodNameForLocalization.#ctor(System.String)")] 13 | [assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "", Scope = "member", Target = "~M:CodingSeb.Localization.LocalizeAttribute.#ctor(System.String)")] 14 | [assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "", Scope = "member", Target = "~M:CodingSeb.Localization.LocPropertyAttribute.#ctor(System.String)")] 15 | [assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "", Scope = "member", Target = "~M:CodingSeb.Localization.LocField.#ctor(System.String)")] 16 | -------------------------------------------------------------------------------- /CodingSeb.Localization.FodyAddin/LocFieldAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CodingSeb.Localization 4 | { 5 | /// 6 | /// To specify the name of a field defined in the class that has this attribute that return a custom instance 7 | /// for multi-users mode 8 | /// 9 | [AttributeUsage(AttributeTargets.Class)] 10 | public class LocFieldAttribute : Attribute 11 | { 12 | /// 13 | /// To specify the name of a field defined in the class that has this attribute that return a custom instance 14 | /// for multi-users mode 15 | /// 16 | /// the name of the field that get the custom instance 17 | public LocFieldAttribute(string fieldName) 18 | {} 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CodingSeb.Localization.FodyAddin/LocPropertyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CodingSeb.Localization 4 | { 5 | /// 6 | /// To specify the name of a property defined in the class that has this attribute that return a custom instance 7 | /// for multi-users mode 8 | /// 9 | [AttributeUsage(AttributeTargets.Class)] 10 | public class LocPropertyAttribute : Attribute 11 | { 12 | /// 13 | /// To specify the name of a property defined in the class that has this attribute that return a custom instance 14 | /// for multi-users mode 15 | /// 16 | /// the name of the property that get the custom instance 17 | public LocPropertyAttribute(string propertyName) 18 | {} 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CodingSeb.Localization.FodyAddin/LocalizeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CodingSeb.Localization 4 | { 5 | /// 6 | /// To specify that a property is a localization and need to has a PropertyChanged event when the current language changed 7 | /// 8 | [AttributeUsage(AttributeTargets.Property)] 9 | public class LocalizeAttribute : Attribute 10 | { 11 | /// 12 | /// To specify that a property is a localization and need to has a PropertyChanged event when the current language changed 13 | /// 14 | public LocalizeAttribute() { } 15 | 16 | /// 17 | /// To specify that a property is a localization and need to has a PropertyChanged event when the current language changed 18 | /// and inject code in the property of the translation 19 | /// 20 | /// The Text Id for the translation to inject in this property 21 | public LocalizeAttribute(string textId) 22 | { } 23 | 24 | /// 25 | /// The default TextTo return if the translation does not exist. 26 | /// 27 | public string DefaultValue { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /CodingSeb.Localization.FodyAddin/PropertyChangedTriggerMethodNameForLocalization.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CodingSeb.Localization 4 | { 5 | /// 6 | /// To specify the name of the method that trigger the PropertyChanged event if not a standard one. 7 | /// The method will be use to triggger PropertyChanged when Current Language changed 8 | /// 9 | [AttributeUsage(AttributeTargets.Class)] 10 | public class PropertyChangedTriggerMethodNameForLocalization : Attribute 11 | { 12 | /// 13 | /// Constructor 14 | /// 15 | /// The name of the method that trigger the PropertyChanged event 16 | public PropertyChangedTriggerMethodNameForLocalization(string methodName) 17 | { } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CodingSeb.Localization.FodyAddin/key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingseb/Localization/d7b668f9de7399081be9ecd8d9ed5f505c751bdf/CodingSeb.Localization.FodyAddin/key.snk -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Coding Seb 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /Localization.Avalonia/Converters/TrLanguageIdConverter.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Data.Converters; 2 | using System; 3 | using System.Globalization; 4 | 5 | namespace CodingSeb.Localization.Avalonia 6 | { 7 | /// 8 | /// Converter to Translate a specific TextId in the Binding LanguageId. 9 | /// If Translation don't exist return DefaultText. 10 | /// Not usable in TwoWay Binding mode. 11 | /// 12 | public class TrLanguageIdConverter : IValueConverter 13 | { 14 | /// 15 | /// To force the use of a specific identifier 16 | /// 17 | public virtual string TextId { get; set; } 18 | 19 | /// 20 | /// The text to return if no text correspond to textId in the current language 21 | /// 22 | public string DefaultText { get; set; } 23 | 24 | /// 25 | /// To provide a prefix to add at the begining of the translated text. 26 | /// 27 | public string Prefix { get; set; } = string.Empty; 28 | 29 | /// 30 | /// To provide a suffix to add at the end of the translated text. 31 | /// 32 | public string Suffix { get; set; } = string.Empty; 33 | 34 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 35 | { 36 | return Prefix + Loc.Tr(TextId, DefaultText?.Replace("[apos]", "'"), value?.ToString()) + Suffix; 37 | } 38 | 39 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); 40 | 41 | public object ProvideValue(IServiceProvider serviceProvider) 42 | { 43 | return this; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Localization.Avalonia/Converters/TrStringFormatConverter.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Data.Converters; 2 | using Avalonia.Markup.Xaml; 3 | using System; 4 | using System.Globalization; 5 | 6 | namespace CodingSeb.Localization.Avalonia.Converters 7 | { 8 | public class TrStringFormatConverter : IValueConverter 9 | { 10 | public TrStringFormatConverter() 11 | { 12 | //WeakEventManager.AddHandler(Loc.Instance, nameof(Loc.Instance.CurrentLanguageChanged), CurrentLanguageChanged); 13 | } 14 | 15 | public TrStringFormatConverter(string textId) 16 | { 17 | TextId = textId; 18 | //WeakEventManager.AddHandler(Loc.Instance, nameof(Loc.Instance.CurrentLanguageChanged), CurrentLanguageChanged); 19 | } 20 | 21 | ~TrStringFormatConverter() 22 | { 23 | //WeakEventManager.RemoveHandler(Loc.Instance, nameof(Loc.Instance.CurrentLanguageChanged), CurrentLanguageChanged); 24 | } 25 | 26 | /// 27 | /// The text to return if no text correspond to textId in the current language 28 | /// 29 | public string DefaultText { get; set; } 30 | 31 | /// 32 | /// The language id in which to get the translation. To Specify if not CurrentLanguage 33 | /// 34 | public string LanguageId { get; set; } 35 | 36 | /// 37 | /// To provide a prefix to add at the begining of the translated text. 38 | /// 39 | public string Prefix { get; set; } = string.Empty; 40 | 41 | /// 42 | /// To provide a suffix to add at the end of the translated text. 43 | /// 44 | public string Suffix { get; set; } = string.Empty; 45 | 46 | /// 47 | /// To force the use of a specific identifier 48 | /// 49 | [ConstructorArgument("textId")] 50 | public string TextId { get; set; } 51 | 52 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => 53 | Prefix + string.Format(string.IsNullOrEmpty(TextId) ? "" : Loc.Tr(TextId, DefaultText?.Replace("[apos]", "'"), LanguageId), value) + Suffix; 54 | 55 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); 56 | 57 | public object ProvideValue(IServiceProvider serviceProvider) => this; 58 | 59 | private void CurrentLanguageChanged(object sender, CurrentLanguageChangedEventArgs e) 60 | { 61 | //if (xamlTargetObject != null && xamlDependencyProperty != null) 62 | //{ 63 | // if (IsInAMultiBinding) 64 | // BindingOperations.GetMultiBindingExpression(xamlTargetObject, xamlDependencyProperty)?.UpdateTarget(); 65 | // else 66 | // BindingOperations.GetBindingExpression(xamlTargetObject, xamlDependencyProperty)?.UpdateTarget(); 67 | //} 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Localization.Avalonia/Converters/TrStringFormatMultiValuesConverter.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Data.Converters; 2 | using Avalonia.Markup.Xaml; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | 7 | namespace CodingSeb.Localization.Avalonia.Converters 8 | { 9 | public class TrStringFormatMultiValuesConverter : IMultiValueConverter 10 | { 11 | public TrStringFormatMultiValuesConverter() 12 | { 13 | //WeakEventManager.AddHandler(Loc.Instance, nameof(Loc.Instance.CurrentLanguageChanged), CurrentLanguageChanged); 14 | } 15 | 16 | public TrStringFormatMultiValuesConverter(string textId) 17 | { 18 | TextId = textId; 19 | //WeakEventManager.AddHandler(Loc.Instance, nameof(Loc.Instance.CurrentLanguageChanged), CurrentLanguageChanged); 20 | } 21 | 22 | ~TrStringFormatMultiValuesConverter() 23 | { 24 | //WeakEventManager.RemoveHandler(Loc.Instance, nameof(Loc.Instance.CurrentLanguageChanged), CurrentLanguageChanged); 25 | } 26 | 27 | /// 28 | /// The text to return if no text correspond to textId in the current language 29 | /// 30 | public string DefaultText { get; set; } 31 | 32 | /// 33 | /// The language id in which to get the translation. To Specify if not CurrentLanguage 34 | /// 35 | public string LanguageId { get; set; } 36 | 37 | /// 38 | /// To force the use of a specific identifier 39 | /// 40 | [ConstructorArgument("textId")] 41 | public string TextId { get; set; } 42 | 43 | /// 44 | /// To provide a prefix to add at the begining of the translated text. 45 | /// 46 | public string Prefix { get; set; } = string.Empty; 47 | 48 | /// 49 | /// To provide a suffix to add at the end of the translated text. 50 | /// 51 | public string Suffix { get; set; } = string.Empty; 52 | 53 | public object Convert(IList values, Type targetType, object parameter, CultureInfo culture) => Prefix + string.Format(string.IsNullOrEmpty(TextId) ? "" : Loc.Tr(TextId, DefaultText?.Replace("[apos]", "'"), LanguageId), values) + Suffix; 54 | 55 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException(); 56 | 57 | public object ProvideValue(IServiceProvider serviceProvider) => this; 58 | 59 | private void CurrentLanguageChanged(object sender, CurrentLanguageChangedEventArgs e) 60 | { 61 | //if (xamlTargetObject != null && xamlDependencyProperty != null) 62 | //{ 63 | // BindingOperations.GetMultiBindingExpression(xamlTargetObject, xamlDependencyProperty)?.UpdateTarget(); 64 | //} 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Localization.Avalonia/Converters/TrTextIdConverter.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Data.Converters; 2 | using System; 3 | using System.Globalization; 4 | 5 | namespace CodingSeb.Localization.Avalonia 6 | { 7 | /// 8 | /// Converter to Translate a the binding textId (In CurrentLanguage or if specified in LanguageId) 9 | /// If Translation don't exist return DefaultText 10 | /// Not usable in TwoWay Binding mode. 11 | /// 12 | public class TrTextIdConverter : IValueConverter 13 | { 14 | public TrTextIdConverter() 15 | { 16 | //WeakEventManager.AddHandler(Loc.Instance, nameof(Loc.Instance.CurrentLanguageChanged), CurrentLanguageChanged); 17 | } 18 | 19 | ~TrTextIdConverter() 20 | { 21 | //WeakEventManager.RemoveHandler(Loc.Instance, nameof(Loc.Instance.CurrentLanguageChanged), CurrentLanguageChanged); 22 | } 23 | 24 | /// 25 | /// The text to return if no text correspond to textId in the current language 26 | /// 27 | public string DefaultText { get; set; } 28 | 29 | /// 30 | /// The language id in which to get the translation. To Specify if not CurrentLanguage 31 | /// 32 | public string LanguageId { get; set; } 33 | 34 | /// 35 | /// A string format where will be injected the binding 36 | /// by Default => {0} 37 | /// 38 | public string TextIdStringFormat { get; set; } = "{0}"; 39 | 40 | /// 41 | /// AllowTo use a converter on the binded value before to inject it in the TextId 42 | /// 43 | public IValueConverter PreConverter { get; set; } 44 | 45 | /// 46 | /// To provide a prefix to add at the begining of the translated text. 47 | /// 48 | public string Prefix { get; set; } = string.Empty; 49 | 50 | /// 51 | /// To provide a suffix to add at the end of the translated text. 52 | /// 53 | public string Suffix { get; set; } = string.Empty; 54 | 55 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 56 | { 57 | string textId = PreConverter?.Convert(value, null, null, null)?.ToString() ?? value?.ToString(); 58 | return Prefix + (string.IsNullOrEmpty(textId) ? "" : Loc.Tr(string.Format(TextIdStringFormat, textId), DefaultText?.Replace("[apos]", "'"), LanguageId)) + Suffix; 59 | } 60 | 61 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); 62 | 63 | public object ProvideValue(IServiceProvider serviceProvider) 64 | { 65 | //SetXamlObjects(serviceProvider); 66 | 67 | return this; 68 | } 69 | 70 | private void CurrentLanguageChanged(object sender, CurrentLanguageChangedEventArgs e) 71 | { 72 | //if (xamlTargetObject != null && xamlDependencyProperty != null) 73 | //{ 74 | // if(IsInAMultiBinding) 75 | // BindingOperations.GetMultiBindingExpression(xamlTargetObject, xamlDependencyProperty)?.UpdateTarget(); 76 | // else 77 | // BindingOperations.GetBindingExpression(xamlTargetObject, xamlDependencyProperty)?.UpdateTarget(); 78 | //} 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Localization.Avalonia/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /Localization.Avalonia/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Used to control if the On_PropertyName_Changed feature is enabled. 12 | 13 | 14 | 15 | 16 | Used to control if the Dependent properties feature is enabled. 17 | 18 | 19 | 20 | 21 | Used to control if the IsChanged property feature is enabled. 22 | 23 | 24 | 25 | 26 | Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form. 27 | 28 | 29 | 30 | 31 | Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project. 32 | 33 | 34 | 35 | 36 | Used to control if equality checks should use the Equals method resolved from the base class. 37 | 38 | 39 | 40 | 41 | Used to control if equality checks should use the static Equals method resolved from the base class. 42 | 43 | 44 | 45 | 46 | Used to turn off build warnings from this weaver. 47 | 48 | 49 | 50 | 51 | Used to turn off build warnings about mismatched On_PropertyName_Changed methods. 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 60 | 61 | 62 | 63 | 64 | A comma-separated list of error codes that can be safely ignored in assembly verification. 65 | 66 | 67 | 68 | 69 | 'false' to turn off automatic generation of the XML Schema file. 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /Localization.Avalonia/Localization.Avalonia.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0;net6.0;netstandard2.0 5 | disable 6 | disable 7 | latest 8 | True 9 | CodingSeb 10 | 11 | Copyright © Coding Seb 2019 12 | 13 | A suite to localize C# and (WPF/AvaloniaUI) project easily base on file format you choose. 14 | https://github.com/codingseb/Localization 15 | https://github.com/codingseb/Localization 16 | true 17 | LICENSE.md 18 | README.md 19 | 1.4.1.0 20 | 1.4.1.0 21 | 22 | * fix: StringFormatArgBinding Breaking things in Tr 23 | 24 | 1.4.1 25 | True 26 | CodingSeb.$(MSBuildProjectName.Replace(" ", "_")) 27 | CodingSeb.Localization.Avalonia 28 | 29 | 30 | 31 | 32 | True 33 | \ 34 | 35 | 36 | True 37 | \ 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Localization.Avalonia/Properties/AssemblyInfos.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Metadata; 2 | 3 | [assembly: XmlnsDefinition("https://github.com/avaloniaui", "CodingSeb.Localization.Avalonia")] -------------------------------------------------------------------------------- /Localization.Avalonia/TrData.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Utilities; 2 | using PropertyChanged; 3 | using System.ComponentModel; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace CodingSeb.Localization.Avalonia 7 | { 8 | /// 9 | /// This class is used as viewModel to bind to DependencyProperties 10 | /// Is use by Tr MarkupExtension to dynamically update the translation when current language changed 11 | /// 12 | internal class TrData : INotifyPropertyChanged 13 | { 14 | private Loc locInstance; 15 | 16 | public TrData() 17 | { 18 | SubscribeToCurrentLanguageChanged(); 19 | } 20 | 21 | ~TrData() 22 | { 23 | UnsubscribeFromCurrentLanguageChanged(); 24 | } 25 | 26 | /// 27 | /// To force the use of a specific identifier 28 | /// 29 | [DoNotNotify] 30 | public string TextId { get; set; } 31 | 32 | /// 33 | /// To Format the Given TextId (useful when binding TextId). 34 | /// Default value : "{0}" 35 | /// 36 | public string TextIdStringFormat { get; set; } = "{0}"; 37 | 38 | /// 39 | /// The text to return if no text correspond to textId in the current language 40 | /// 41 | [DoNotNotify] 42 | public string DefaultText { get; set; } 43 | 44 | /// 45 | /// The language id in which to get the translation. To Specify if not CurrentLanguage 46 | /// 47 | public string LanguageId { get; set; } 48 | 49 | /// 50 | /// To provide a prefix to add at the begining of the translated text. 51 | /// 52 | public string Prefix { get; set; } = string.Empty; 53 | 54 | /// 55 | /// To provide a suffix to add at the end of the translated text. 56 | /// 57 | public string Suffix { get; set; } = string.Empty; 58 | 59 | /// 60 | /// An optional object use as data that is represented by this translation 61 | /// (Example used for Enum values translation) 62 | /// 63 | public object Data { get; set; } 64 | 65 | /// 66 | /// An optional object use as model for Formatting the translated string 67 | /// (Example used for Pluralisation, Injection, Tenary) 68 | /// 69 | [DoNotNotify] 70 | public object Model { get; set; } 71 | 72 | /// 73 | /// The Loc instance to use to perform the translation. 74 | /// if null it will use Loc.Instance 75 | /// 76 | [DoNotNotify] 77 | public Loc LocInstance 78 | { 79 | get => locInstance; 80 | 81 | set 82 | { 83 | UnsubscribeFromCurrentLanguageChanged(); 84 | locInstance=value; 85 | SubscribeToCurrentLanguageChanged(); 86 | } 87 | } 88 | 89 | /// 90 | /// When the current Language changed update the binding (Call OnPropertyChanged) 91 | /// 92 | /// 93 | /// 94 | private void CurrentLanguageChanged(object sender, CurrentLanguageChangedEventArgs e) 95 | { 96 | OnPropertyChanged(nameof(TranslatedText)); 97 | } 98 | 99 | /// 100 | /// Get final translated text 101 | /// 102 | public string TranslatedText 103 | { 104 | get 105 | { 106 | string translatedText; 107 | if (Model != null) 108 | { 109 | translatedText = (locInstance ?? Loc.Instance).Translate(string.Format(TextIdStringFormat, TextId), Model, DefaultText, LanguageId); 110 | } 111 | else 112 | { 113 | translatedText = (locInstance ?? Loc.Instance).Translate(string.Format(TextIdStringFormat, TextId), DefaultText, LanguageId); 114 | } 115 | 116 | return Prefix + translatedText + Suffix; 117 | } 118 | } 119 | 120 | private void SubscribeToCurrentLanguageChanged() 121 | { 122 | WeakEventHandlerManager.Subscribe(locInstance ?? Loc.Instance, nameof(Loc.CurrentLanguageChanged), CurrentLanguageChanged); 123 | } 124 | 125 | private void UnsubscribeFromCurrentLanguageChanged() 126 | { 127 | WeakEventHandlerManager.Unsubscribe(locInstance ?? Loc.Instance, nameof(Loc.CurrentLanguageChanged), CurrentLanguageChanged); 128 | } 129 | 130 | public event PropertyChangedEventHandler PropertyChanged; 131 | 132 | public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") 133 | { 134 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Localization.Avalonia/TrViewModel.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Markup.Xaml; 2 | using Avalonia.Utilities; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Dynamic; 7 | using System.Runtime.CompilerServices; 8 | 9 | namespace CodingSeb.Localization.Avalonia 10 | { 11 | public class TrViewModel : MarkupExtension 12 | { 13 | public override object ProvideValue(IServiceProvider serviceProvider) 14 | { 15 | return new TrViewModelData(); 16 | } 17 | } 18 | 19 | public class TrViewModelData : DynamicObject, INotifyPropertyChanged 20 | { 21 | private readonly List textIdsList = new(); 22 | 23 | public TrViewModelData() 24 | { 25 | WeakEventHandlerManager.Subscribe(Loc.Instance, nameof(Loc.CurrentLanguageChanged), CurrentLanguageChanged); 26 | } 27 | 28 | ~TrViewModelData() 29 | { 30 | WeakEventHandlerManager.Unsubscribe(Loc.Instance, nameof(Loc.CurrentLanguageChanged), CurrentLanguageChanged); 31 | } 32 | 33 | private void CurrentLanguageChanged(object sender, CurrentLanguageChangedEventArgs args) 34 | { 35 | textIdsList.ForEach(NotifyPropertyChanged); 36 | } 37 | 38 | public event PropertyChangedEventHandler PropertyChanged; 39 | 40 | public virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "") 41 | { 42 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 43 | } 44 | 45 | public override bool TryGetMember(GetMemberBinder binder, out object result) 46 | { 47 | if (!textIdsList.Contains(binder.Name)) 48 | textIdsList.Add(binder.Name); 49 | 50 | result = Loc.Tr(binder.Name); 51 | 52 | return true; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Localization.AvaloniaExample/App.axaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Localization.AvaloniaExample/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.Markup.Xaml; 4 | using CodingSeb.Localization.AvaloniaExample.ViewModels; 5 | using CodingSeb.Localization.AvaloniaExample.Views; 6 | using CodingSeb.Localization.AvaloniaExamples; 7 | using PropertyChanged; 8 | using System; 9 | 10 | namespace CodingSeb.Localization.AvaloniaExample 11 | { 12 | [DoNotNotify] 13 | public partial class App : Application 14 | { 15 | public override void Initialize() 16 | { 17 | GC.KeepAlive(typeof(CodingSeb.Localization.Avalonia.Tr).Assembly); 18 | Languages.Init(); 19 | AvaloniaXamlLoader.Load(this); 20 | } 21 | 22 | public override void OnFrameworkInitializationCompleted() 23 | { 24 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 25 | { 26 | desktop.MainWindow = new MainWindow 27 | { 28 | DataContext = new MainWindowViewModel(), 29 | }; 30 | } 31 | 32 | base.OnFrameworkInitializationCompleted(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Localization.AvaloniaExample/Assets/avalonia-logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingseb/Localization/d7b668f9de7399081be9ecd8d9ed5f505c751bdf/Localization.AvaloniaExample/Assets/avalonia-logo.ico -------------------------------------------------------------------------------- /Localization.AvaloniaExample/Converters/BoolToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Data.Converters; 2 | using System; 3 | using System.Globalization; 4 | 5 | namespace CodingSeb.Localization.AvaloniaExample.Converters 6 | { 7 | public class BoolToStringConverter : IValueConverter 8 | { 9 | public string FalseValue { get; set; } 10 | 11 | public string TrueValue { get; set; } 12 | 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | return (bool)value ? TrueValue : FalseValue; 16 | } 17 | 18 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 19 | { 20 | throw new NotImplementedException(); 21 | } 22 | 23 | public object ProvideValue(IServiceProvider serviceProvider) 24 | { 25 | return this; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Localization.AvaloniaExample/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /Localization.AvaloniaExample/Localization.AvaloniaExample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net8.0 6 | disable 7 | 8 | copyused 9 | true 10 | CodingSeb.$(MSBuildProjectName.Replace(" ", "_")) 11 | CodingSeb.Localization.AvaloniaExample 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Localization.AvaloniaExample/Program.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.ReactiveUI; 4 | using System; 5 | 6 | namespace CodingSeb.Localization.AvaloniaExample 7 | { 8 | internal class Program 9 | { 10 | // Initialization code. Don't use any Avalonia, third-party APIs or any 11 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized 12 | // yet and stuff might break. 13 | [STAThread] 14 | public static void Main(string[] args) => BuildAvaloniaApp() 15 | .StartWithClassicDesktopLifetime(args); 16 | 17 | // Avalonia configuration, don't remove; also used by visual designer. 18 | public static AppBuilder BuildAvaloniaApp() 19 | => AppBuilder.Configure() 20 | .UsePlatformDetect() 21 | .LogToTrace() 22 | .UseReactiveUI(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Localization.AvaloniaExample/Utils/Languages.cs: -------------------------------------------------------------------------------- 1 | using CodingSeb.Localization.Loaders; 2 | using System.IO; 3 | using System.Reflection; 4 | 5 | namespace CodingSeb.Localization.AvaloniaExamples 6 | { 7 | public static class Languages 8 | { 9 | private static readonly string languagesFilesDirectory = Path.Combine( 10 | Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), 11 | "lang"); 12 | 13 | public static void Init() 14 | { 15 | Loc.Instance.LogOutMissingTranslations = true; 16 | 17 | LocalizationLoader.Instance.FileLanguageLoaders.Add(new JsonFileLoader()); 18 | 19 | ReloadFiles(); 20 | } 21 | 22 | public static void ReloadFiles() 23 | { 24 | string exampleFileFileName = Path.Combine(languagesFilesDirectory, "Example1.loc.json"); 25 | LocalizationLoader.Instance.ClearAllTranslations(); 26 | LocalizationLoader.Instance.AddFile(exampleFileFileName); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Localization.AvaloniaExample/ViewLocator.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Controls.Templates; 3 | using CodingSeb.Localization.AvaloniaExample.ViewModels; 4 | using System; 5 | 6 | namespace CodingSeb.Localization.AvaloniaExample 7 | { 8 | public class ViewLocator : IDataTemplate 9 | { 10 | Control ITemplate.Build(object data) 11 | { 12 | var name = data.GetType().FullName!.Replace("ViewModel", "View"); 13 | var type = Type.GetType(name); 14 | 15 | if (type != null) 16 | { 17 | return (Control)Activator.CreateInstance(type)!; 18 | } 19 | else 20 | { 21 | return new TextBlock { Text = "Not Found: " + name }; 22 | } 23 | } 24 | 25 | public bool Match(object data) 26 | { 27 | return data is ViewModelBase; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Localization.AvaloniaExample/ViewModels/ItemViewModel.cs: -------------------------------------------------------------------------------- 1 | using ReactiveUI; 2 | 3 | namespace CodingSeb.Localization.AvaloniaExample.ViewModels 4 | { 5 | public class ItemViewModel : ReactiveObject 6 | { 7 | public string ContentName { get; set; } 8 | public int OtherValue { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Localization.AvaloniaExample/ViewModels/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Linq; 4 | 5 | namespace CodingSeb.Localization.AvaloniaExample.ViewModels 6 | { 7 | public class MainWindowViewModel : ViewModelBase 8 | { 9 | private string label; 10 | 11 | public Loc LanguagesManager => Loc.Instance; 12 | 13 | public List Labels => LanguagesManager.TranslationsDictionary 14 | .Keys.ToList() 15 | .FindAll(k => k.StartsWith("Text:")); 16 | 17 | public bool VisibilityForText { get; set; } 18 | 19 | [Localize("ANiceText")] 20 | public string AutoTranslation { get; set; } 21 | 22 | public string Label 23 | { 24 | get 25 | { 26 | label ??= Labels[0]; 27 | return label; 28 | } 29 | set { label = value; } 30 | } 31 | 32 | public ObservableCollection Items { get; set; } = new ObservableCollection() 33 | { 34 | new ItemViewModel() 35 | { 36 | ContentName="Text:Hello", 37 | OtherValue = 1 38 | }, 39 | new ItemViewModel() 40 | { 41 | ContentName="Text:HowAreYou", 42 | OtherValue=2 43 | } 44 | }; 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Localization.AvaloniaExample/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using ReactiveUI; 2 | 3 | namespace CodingSeb.Localization.AvaloniaExample.ViewModels 4 | { 5 | public class ViewModelBase : ReactiveObject 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Localization.AvaloniaExample/Views/MainWindow.axaml: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |