├── .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