KGy SOFT

DynamicResourceManager Class

KGy SOFT Core Libraries Help
Represents a resource manager that provides convenient access to culture-specific resources at run time. As it is derived from HybridResourceManager, it can handle both compiled resources from .dll and .exe files, and XML resources from .resx files at the same time. Based on the selected strategies when a resource is not found in a language it can automatically add the missing resource from a base culture or even create completely new resource sets and save them into .resx files. For text entries the untranslated elements will be marked so they can be found easily for translation.
See the Remarks section for examples and for the differences compared to HybridResourceManager class.
Inheritance Hierarchy

SystemObject
  System.ResourcesResourceManager
    KGySoft.ResourcesHybridResourceManager
      KGySoft.ResourcesDynamicResourceManager

Namespace:  KGySoft.Resources
Assembly:  KGySoft.CoreLibraries (in KGySoft.CoreLibraries.dll) Version: 5.0.0-alpha.2
Syntax

[SerializableAttribute]
public class DynamicResourceManager : HybridResourceManager

The DynamicResourceManager type exposes the following members.

Constructors

  NameDescription
Public methodDynamicResourceManager(Type, String)
Creates a new instance of DynamicResourceManager class that looks up resources in compiled assemblies and resource XML files based on information from the specified Type object.
Public methodDynamicResourceManager(String, Assembly, String)
Creates a new instance of DynamicResourceManager class that looks up resources in compiled assemblies and resource XML files based on information from the specified baseName and assembly.
Top
Properties

  NameDescription
Public propertyAutoAppend
When UseLanguageSettings is , gets or sets the resource auto append options. When UseLanguageSettings is , auto appending of resources is controlled by LanguageSettings.DynamicResourceManagersAutoAppend property.
Default value: AppendFirstNeutralCulture, AppendOnLoad
Public propertyAutoSave
When UseLanguageSettings is , gets or sets the auto saving options. When UseLanguageSettings is , auto saving is controlled by LanguageSettings.DynamicResourceManagersAutoSave property.
Default value: None
Public propertyBaseName
Gets the root name of the resource files that the ResourceManager searches for resources.
(Inherited from ResourceManager.)
Public propertyCloneValues
Gets or sets whether GetObject and GetMetaObject methods return always a new copy of the stored values.
Default value: .
(Inherited from HybridResourceManager.)
Public propertyCompatibleFormat
Gets or sets whether the .resx files should use a compatible format when the resources are automatically saved.
Default value:
Protected propertyFallbackLocation
Gets or sets the location from which to retrieve default fallback resources.
(Inherited from ResourceManager.)
Public propertyIgnoreCase
Gets or sets a value that indicates whether the resource manager allows case-insensitive resource lookups in the GetString/GetMetaString and GetObject/GetMetaObject methods.
(Inherited from HybridResourceManager.)
Public propertyIsDisposed
Gets whether this HybridResourceManager instance is disposed.
(Inherited from HybridResourceManager.)
Public propertyIsModified
Gets whether this HybridResourceManager instance has modified and unsaved data.
(Inherited from HybridResourceManager.)
Protected propertyNeutralResourcesCulture
Gets the CultureInfo that is specified as neutral culture in the Assembly used to initialized this instance, or the CultureInfo.InvariantCulture if no such culture is defined.
(Inherited from HybridResourceManager.)
Public propertyResourceSetType
Gets the type of the resource set object that the HybridResourceManager uses to create a resource set for the binary resources. Please note that for .resx based resources other types can be created as well.
(Inherited from HybridResourceManager.)
Public propertyResXResourcesDir
Gets or sets the relative path to .resx resource files.
Default value: Resources
(Inherited from HybridResourceManager.)
Public propertySafeMode
Gets or sets whether the HybridResourceManager works in safe mode. In safe mode the retrieved objects returned from .resx sources are not deserialized automatically.
See the Remarks section for details.
Default value: .
(Inherited from HybridResourceManager.)
Public propertySource
When UseLanguageSettings is , gets or sets the source, from which the resources should be taken. When UseLanguageSettings is , the source is controlled by LanguageSettings.DynamicResourceManagersSource property.
Default value: CompiledAndResX
(Overrides HybridResourceManagerSource.)
Public propertyThrowException
Gets or sets whether a MissingManifestResourceException should be thrown when a resource .resx file or compiled manifest is not found even for the neutral culture.
Default value: .
(Inherited from HybridResourceManager.)
Public propertyUseLanguageSettings
Gets or sets whether values of AutoAppend, AutoSave and Source properties are taken centrally from the LanguageSettings class.
Default value: .
Top
Methods

  NameDescription
Public methodDispose
Disposes the resources of the current instance.
(Inherited from HybridResourceManager.)
Protected methodDispose(Boolean)
Releases unmanaged and - optionally - managed resources.
(Overrides HybridResourceManagerDispose(Boolean).)
Public methodEquals
Determines whether the specified object is equal to the current object.
(Inherited from Object.)
Protected methodFinalize
Allows an object to try to free resources and perform other cleanup operations before it is reclaimed by garbage collection.
(Inherited from Object.)
Public methodGetExpandoResourceSet
Retrieves the resource set for a particular culture, which can be dynamically modified.
(Overrides HybridResourceManagerGetExpandoResourceSet(CultureInfo, ResourceSetRetrieval, Boolean).)
Public methodGetHashCode
Serves as the default hash function.
(Inherited from Object.)
Public methodGetMetaObject
Returns the value of the specified non-string metadata for the specified culture.
(Inherited from HybridResourceManager.)
Public methodGetMetaStream
Returns a MemoryStream instance from the metadata of the specified name and culture.
(Inherited from HybridResourceManager.)
Public methodGetMetaString
Returns the value of the string metadata for the specified culture.
(Inherited from HybridResourceManager.)
Public methodGetObject(String)
Returns the value of the specified resource.
(Overrides HybridResourceManagerGetObject(String).)
Public methodGetObject(String, CultureInfo)
Gets the value of the specified resource localized for the specified culture.
(Overrides HybridResourceManagerGetObject(String, CultureInfo).)
Protected methodGetResourceFileName
Generates the name of the resource file for the given CultureInfo object.
(Inherited from ResourceManager.)
Public methodGetResourceSet
Retrieves the resource set for a particular culture.
(Overrides HybridResourceManagerGetResourceSet(CultureInfo, Boolean, Boolean).)
Public methodGetStream(String)
Returns a MemoryStream instance from the resource of the specified name.
(Overrides HybridResourceManagerGetStream(String).)
Public methodGetStream(String, CultureInfo)
Returns a MemoryStream instance from the resource of the specified name and culture.
(Overrides HybridResourceManagerGetStream(String, CultureInfo).)
Public methodGetString(String)
Returns the value of the specified string resource.
(Overrides HybridResourceManagerGetString(String).)
Public methodGetString(String, CultureInfo)
Returns the value of the string resource localized for the specified culture.
(Overrides HybridResourceManagerGetString(String, CultureInfo).)
Public methodGetType
Gets the Type of the current instance.
(Inherited from Object.)
Protected methodInternalGetResourceSet
Provides the implementation for finding a resource set.
(Overrides HybridResourceManagerInternalGetResourceSet(CultureInfo, Boolean, Boolean).)
Protected methodMemberwiseClone
Creates a shallow copy of the current Object.
(Inherited from Object.)
Public methodReleaseAllResources
Tells the resource manager to call the ResourceSet.Close method on all ResourceSet objects and release all resources. All unsaved resources will be lost.
(Overrides HybridResourceManagerReleaseAllResources.)
Public methodRemoveMetaObject
Removes a metadata object from the current HybridResourceManager with the specified name for the specified culture.
(Inherited from HybridResourceManager.)
Public methodRemoveObject
Removes a resource object from the current DynamicResourceManager with the specified name for the specified culture.
(Overrides HybridResourceManagerRemoveObject(String, CultureInfo).)
Public methodSaveAllResources
Saves all already loaded resources.
(Inherited from HybridResourceManager.)
Public methodSaveResourceSet
Saves the resource set of a particular culture if it has been already loaded.
(Inherited from HybridResourceManager.)
Public methodSetMetaObject
Adds or replaces a metadata object in the current HybridResourceManager with the specified name for the specified culture.
(Inherited from HybridResourceManager.)
Public methodSetObject
Adds or replaces a resource object in the current DynamicResourceManager with the specified name for the specified culture.
(Overrides HybridResourceManagerSetObject(String, Object, CultureInfo).)
Public methodToString
Returns a string that represents the current object.
(Inherited from Object.)
Top
Events

  NameDescription
Public eventStatic memberAutoSaveError
Occurs when an exception is thrown on auto saving. If this event is not subscribed, the following exception types are automatically suppressed, as they can occur on save: IOException, SecurityException, UnauthorizedAccessException. If such an exception is suppressed some resources might remain unsaved. Though the event is a static one, the sender of the handler is the corresponding DynamicResourceManager instance. Thus the save failures of the non public DynamicResourceManager instances (eg. resource managers of an assembly) can be tracked, too.
Top
Fields

  NameDescription
Protected fieldBaseNameField
Specifies the root name of the resource files that the ResourceManager searches for resources.
(Inherited from ResourceManager.)
Protected fieldMainAssembly
Specifies the main assembly that contains the resources.
(Inherited from ResourceManager.)
Protected fieldResourceSets Obsolete.
Contains a Hashtable that returns a mapping from cultures to ResourceSet objects.
(Inherited from ResourceManager.)
Top
Extension Methods

  NameDescription
Public Extension MethodConvert(Type, CultureInfo)Overloaded.
Converts an Object specified in the obj parameter to the desired targetType.
See the Examples section of the generic ConvertTTarget(Object, CultureInfo) overload for an example.
(Defined by ObjectExtensions.)
Public Extension MethodCode exampleConvertTTarget(CultureInfo)Overloaded.
Converts an Object specified in the obj parameter to the desired TTarget.
(Defined by ObjectExtensions.)
Public Extension MethodIn (Defined by ObjectExtensions.)
Public Extension MethodTryConvert(Type, Object)Overloaded.
Tries to convert an Object specified in the obj parameter to the desired targetType.
(Defined by ObjectExtensions.)
Public Extension MethodTryConvert(Type, CultureInfo, Object)Overloaded.
Tries to convert an Object specified in the obj parameter to the desired targetType.
(Defined by ObjectExtensions.)
Public Extension MethodTryConvertTTarget(TTarget)Overloaded.
Tries to convert an Object specified in the obj parameter to the desired TTarget.
See the Examples section of the ConvertTTarget(Object, CultureInfo) method for a related example.
(Defined by ObjectExtensions.)
Public Extension MethodTryConvertTTarget(CultureInfo, TTarget)Overloaded.
Tries to convert an Object specified in the obj parameter to the desired TTarget.
See the Examples section of the ConvertTTarget(Object, CultureInfo) method for a related example.
(Defined by ObjectExtensions.)
Top
Remarks

DynamicResourceManager class is derived from HybridResourceManager and adds the functionality of automatic appending the resources with the non-translated and/or unknown entries as well as auto-saving the changes. This makes possible to automatically create the .resx files if the language of the application is changed to a language, which has no translation yet. See also the static LanguageSettings class. The strategy of auto appending and saving can be chosen by the AutoAppend and AutoSave properties (see AutoAppendOptions and AutoSaveOptions enumerations).

Tip Tip
To see when to use the ResXResourceReader, ResXResourceWriter, ResXResourceSet, ResXResourceManager, HybridResourceManager and DynamicResourceManager classes see the documentation of the KGySoft.Resources namespace.

DynamicResourceManager combines the functionality of ResourceManager, ResXResourceManager, HybridResourceManager and extends these with the feature of auto expansion. It can be an ideal choice to use it as a resource manager of an application or a class library because it gives you freedom (or to the consumer of your library) to choose the strategy. If AutoAppend and AutoSave functionalities are completely disabled, then it is equivalent to a HybridResourceManager, which can handle resources both from compiled and XML sources but you must explicitly add new content and save it (see the example of the HybridResourceManager base class). If you restrict even the source of the resources, then you can get the functionality of the ResXResourceManager class (Source is ResXOnly), or the ResourceManager class (Source is CompiledOnly).

Additional features compared to HybridResourceManager

Centralized vs. individual settings:
The behavior of DynamicResourceManager instances can be controlled in two ways, which can be configured by the UseLanguageSettings property.

  • Individual control - If UseLanguageSettings is , which is the default value, then the DynamicResourceManager behavior is simply determined by its own properties. This can be alright for short-living DynamicResourceManager instances (for example, in a using block), or when you are sure you don't want let the consumers of your library to customize the settings of resource managers.
  • Centralized control - If you use the DynamicResourceManager class as the resource manager of a class library, you might want to let the consumers of your library to control how DynamicResourceManager instances should behave. For example, maybe one consumer wants to allow generating new .resx language files by using custom AutoAppendOptions, and another one does not want to let generating .resx files at all. If UseLanguageSettings is , then AutoAppend, AutoSave and Source properties will be taken from the static LanguageSettings class (see LanguageSettings.DynamicResourceManagersAutoAppend, LanguageSettings.DynamicResourceManagersAutoSave and LanguageSettings.DynamicResourceManagersSource properties, respectively). This makes possible to control the behavior of DynamicResourceManager instances, which enable centralized settings, without exposing these manager instances of our class library to the public. See also the example at the Recommended usage for string resources in a class library section.
Note Note
  • Unlike the Source property, LanguageSettings.DynamicResourceManagersSource property is CompiledOnly by default, ensuring that centralized DynamicResourceManager instances work the same way as regular ResourceManager classes by default, so the application can opt-in dynamic creation of .resx files, for example in its Main method.
  • Turning on the UseLanguageSettings property makes the DynamicResourceManager to subscribe multiple events. If such a DynamicResourceManager is used in a non-static or short-living context make sure to dispose it to prevent leaking resources.

Auto Appending:
The automatic expansion of the resources can be controlled by the AutoAppend property (or by LanguageSettings.DynamicResourceManagersAutoAppend, property, if UseLanguageSettings is ), and it covers three different strategies, which can be combined:

  1. Unknown resources - If AddUnknownToInvariantCulture option is enabled and an unknown resource is requested, then the resource set of the invariant culture will be appended by the newly requested resource. It also means that MissingManifestResourceException will never be thrown, even if ThrowException property is . If the unknown resource is requested by GetString methods, then the value of the requested name will be the name itself prefixed by the LanguageSettings.UnknownResourcePrefix property. On the other hand, if the unknown resource is requested by the GetObject methods, then a  value will be added.
    C#
    using System;
    using KGySoft.Resources;
    
    class Example
    {
        static void Main(string[] args)
        {
            var manager = new DynamicResourceManager(typeof(Example));
            manager.AutoAppend = AutoAppendOptions.AddUnknownToInvariantCulture;
    
            // Without the option above the following line would throw a MissingManifestResourceException
            Console.WriteLine(manager.GetString("UnknownString")); // prints [U]UnknownString
            Console.WriteLine(manager.GetObject("UnknownObject")); // prints empty line
    
            manager.SaveAllResources(compatibleFormat: false);
        }
    }
    The example above creates a Resources\Example.resx file under the binary output folder of the console application with the following content:
    XML
    <?xml version="1.0"?>
    <root>
      <data name="UnknownString">
        <value>[U]UnknownString</value>
      </data>
      <assembly alias="KGySoft.CoreLibraries" name="KGySoft.CoreLibraries, Version=3.6.3.1, Culture=neutral, PublicKeyToken=b45eba277439ddfe" />
      <data name="UnknownObject" type="KGySoft.Resources.ResXNullRef, KGySoft.Libraries">
        <value />
      </data>
    </root>
  2. Appending neutral and specific cultures - The previous section was about expanding the resource file of the invariant culture represented by the CultureInfo.InvariantCulture property. Every other CultureInfo instance can be classified as either a neutral or specific culture. Neutral cultures are region independent (eg. en is the English culture in general), whereas specific cultures are related to a specific region (eg. en-US is the American English culture). The parent of a specific culture can be another specific or a neutral one, and the parent of a neutral culture can be another neutral or the invariant culture. In most cases there is one specific and one neutral culture in a full chain, for example:
    en-US (specific) -> en (neutral) -> Invariant
    But sometimes there can be more neutral cultures:
    ku-Arab-IR (specific) -> ku-Arab (neutral) -> ku (neutral) -> Invariant
    Or more specific cultures:
    ca-ES-valencia (specific) -> ca-es (specific) -> ca (neutral) -> Invariant
    There are two groups of options, which control where the untranslated resources should be merged from and to:
    1. AppendFirstNeutralCulture, AppendLastNeutralCulture and AppendNeutralCultures options will append the neutral cultures (eg. en) if a requested resource is found in the invariant culture.
    2. AppendFirstSpecificCulture and AppendSpecificCultures options will append the specific cultures (eg. en-US) if a requested resource is found in any parent culture. AppendLastSpecificCulture does the same, except that the found resource must be in the resource set of a non-specific culture..
    If the merged resource is a String, then the value of the existing resource will be prefixed by the LanguageSettings.UntranslatedResourcePrefix property, and this prefixed string will be saved in the target resource; otherwise, the original value will be duplicated in the target resource.
    Note Note
    "First" and "last" terms above refer the first and last neutral/specific cultures in the order from most specific to least specific one as in the examples above. See the descriptions of the referred AutoAppendOptions options for more details and for examples with a fully artificial culture hierarchy with multiple neutral and specific cultures.
    C#
    using System;
    using System.Globalization;
    using KGySoft;
    using KGySoft.Resources;
    
    class Example
    {
        private static CultureInfo enUS = CultureInfo.GetCultureInfo("en-US");
        private static CultureInfo en = enUS.Parent;
        private static CultureInfo inv = en.Parent;
    
        static void Main(string[] args)
        {
            var manager = new DynamicResourceManager(typeof(Example));
    
            // this will cause to copy the non-existing entries from invariant to "en"
            manager.AutoAppend = AutoAppendOptions.AppendFirstNeutralCulture;
    
            // preparation
            manager.SetObject("TestString", "Test string in invariant culture", inv);
            manager.SetObject("TestString", "Test string in English culture", en);
            manager.SetObject("TestString2", "Another test string in invariant culture", inv);
            manager.SetObject("TestObject", 42, inv);
            manager.SetObject("DontCareObject", new byte[0], inv);
    
            // setting the UI culture so we do not need to specify the culture in GetString/Object
            LanguageSettings.DisplayLanguage = enUS;
    
            Console.WriteLine(manager.GetString("TestString")); // already exists in en
            Console.WriteLine(manager.GetString("TestString2")); // copied to en with prefix
            Console.WriteLine(manager.GetObject("TestObject")); // copied to en
            // Console.WriteLine(manager.GetObject("DontCareObject")); // not copied because not accessed
    
            // saving the changes
            manager.SaveAllResources(compatibleFormat: false);
        }
    }
    The example above creates the Example.resx and Example.en.resx files. No Example.en-US.resx is created because we chose appending the first neutral culture only. The content of Example.en.resx will be the following:
    XML
    <?xml version="1.0"?>
    <root>
      <data name="TestString">
        <value>Test string in English culture</value>
      </data>
      <data name="TestString2">
        <value>[T]Another test string in invariant culture</value>
      </data>
      <data name="TestObject" type="System.Int32">
        <value>42</value>
      </data>
    </root>
    By looking for '[T]' occurrences we can easily find the merged strings to translate.
  3. Merging complete resource sets - The example above demonstrates how the untranslated entries will be applied to the target language files. However, in that example only the actually requested entries will be copied on demand. It is possible that we want to generate a full language file in order to be able to make complete translations. If that is what we need we can use the AppendOnLoad option. This option should be used together with at least one of the options from the previous point to have any effect.
    C#
    using System;
    using System.Globalization;
    using System.Resources;
    using KGySoft;
    using KGySoft.Resources;
    
    // we can tell what the language of the invariant resource is
    [assembly: NeutralResourcesLanguage("en")]
    
    class Example
    {
        static void Main(string[] args)
        {
            var manager = new DynamicResourceManager(typeof(Example));
    
            // actually this is the default option:
            manager.AutoAppend = AutoAppendOptions.AppendFirstNeutralCulture | AutoAppendOptions.AppendOnLoad;
    
            // we prepare only the invariant resource
            manager.SetObject("TestString", "Test string", CultureInfo.InvariantCulture);
            manager.SetObject("TestString2", "Another test string", CultureInfo.InvariantCulture);
            manager.SetObject("TestObject", 42, CultureInfo.InvariantCulture);
            manager.SetObject("AnotherObject", new byte[0], CultureInfo.InvariantCulture);
    
            // Getting an English resource will not create the en.resx file because this is
            // the default language of our application thanks to the NeutralResourcesLanguage attribute
            LanguageSettings.DisplayLanguage = CultureInfo.GetCultureInfo("en-US");
            Console.WriteLine(manager.GetString("TestString")); // Displays "Test string", no resource is created
    
            LanguageSettings.DisplayLanguage = CultureInfo.GetCultureInfo("fr-FR");
            Console.WriteLine(manager.GetString("TestString")); // Displays "[T]Test string", resource "fr" is created
    
            // saving the changes
            manager.SaveAllResources(compatibleFormat: false);
        }
    }
    The example above creates Example.resx and Example.fr.resx files. Please note that no Example.en.resx is created because the NeutralResourcesLanguageAttribute indicates that the language of the invariant resource is English. If we open the created Example.fr.resx file we can see that every resource was copied from the invariant resource even though we accessed a single item:
    XML
    <?xml version="1.0"?>
    <root>
      <data name="TestString">
        <value>[T]Test string</value>
      </data>
      <data name="TestString2">
        <value>[T]Another test string</value>
      </data>
      <data name="TestObject" type="System.Int32">
        <value>42</value>
      </data>
      <data name="AnotherObject" type="System.Byte[]">
        <value />
      </data>
    </root>
Note Note
Please note that auto appending affects resources only. Metadata are never merged.

Auto Saving:
By setting the AutoSave property (or LanguageSettings.DynamicResourceManagersAutoSave, if UseLanguageSettings is ), the DynamicResourceManager is able to save the dynamically created content automatically on specific events:

  • Changing the display language - If LanguageChange option is enabled the changes are saved whenever the current UI culture is set via the LanguageSettings.DisplayLanguage property.
    Note Note
    Enabling this option makes the DynamicResourceManager to subscribe to the static LanguageSettings.DisplayLanguageChanged event. To prevent leaking resources make sure to dispose the DynamicResourceManager if it is used in a non-static or short-living context.
  • Application exit - If DomainUnload option is enabled the changes are saved when current AppDomain is being unloaded, including the case when the application exits.
    Note Note
    Enabling this option makes the DynamicResourceManager to subscribe to the AppDomain.ProcessExit or AppDomain.DomainUnload event. To prevent leaking resources make sure to dispose the DynamicResourceManager if it is used in a non-static or short-living context. However, to utilize saving changes on application exit or domain unload, DynamicResourceManager is best to be used in a static context.
  • Changing resource source - If Source property changes it may cause data loss in terms of unsaved changes. To prevent this SourceChange option can be enabled so the changes will be saved before actualizing the new value of the Source property.
  • Disposing - Enabling the Dispose option makes possible to save changes automatically when the DynamicResourceManager is being disposed.
    C#
    using System.Globalization;
    using KGySoft.Resources;
    
    class Example
    {
        static void Main(string[] args)
        {
            // thanks to the AutoSave = Dispose the Example.resx will be created at the end of the using block
            using (var manager = new DynamicResourceManager(typeof(Example)) { AutoSave = AutoSaveOptions.Dispose })
            {
                manager.SetObject("Test string", "Test value", CultureInfo.InvariantCulture);
            }
        }
    }

Considering SaveAllResources is indirectly called on auto save, you cannot set its parameters directly. However, by setting the CompatibleFormat property, you can tell whether the result .resx files should be able to be read by a System.Resources.ResXResourceReader instance and the Visual Studio Resource Editor. If it is  the result .resx files are often shorter, and the values can be deserialized with better accuracy (see the remarks at ResXResourceWriter), but the result can be read only by the ResXResourceReader class.

Normally, if you save the changes by the SaveAllResources method, you can handle the possible exceptions locally. To handle errors occurred during auto save you can subscribe the AutoSaveError event.

Recommended usage for string resources in a class library 

A class library can be used by any consumers who want to use the features of that library. If it contains resources it can be useful if we allow the consumer of our class library to create translations for it in any language. In an application it can be the decision of the consumer whether generating new XML resource (.resx) files should be allowed or not. If so, we must be prepared for invalid files or malicious content (for example, the .resx file can contain serialized data of any type, whose constructor can run any code when deserialized). The following example takes all of these aspects into consideration.

Note Note
In the following example there is a single compiled resource created in a class library, without any satellite assemblies. The additional language files can be generated at run-time if the consumer application allows it.
  1. Create a new project (Class Library)
    New class library
  2. Delete the automatically created Class1.cs
  3. Create a new class: Res. The class will be  and internal, and it will contain the resource manager for this class library. The initial content of the file will be the following:
    C#
    using System;
    using KGySoft;
    using KGySoft.Resources;
    
    namespace ClassLibrary1
    {
        internal static class Res
        {
            // internal resources for errors
            private const string unavailableResource = "Resource ID not found: {0}";
            private const string invalidResource = "Resource text is not valid for {0} arguments: {1}";
    
            private static readonly DynamicResourceManager resourceManager =
                // the name of the compiled resources must match (see also point 5)
                new DynamicResourceManager("ClassLibrary1.Messages", typeof(Res).Assembly)
                {
                    SafeMode = true,
                    UseLanguageSettings = true,
                    ThrowException = false,
                    // CompatibleFormat = true // use this if you want to edit the result files with VS resource editor
                };
    
            // Here will be the properties for the resources. This one is private because used from this class.
            private static string NullReference => Get(nameof(NullReference));
    
            // [...] Your resources can be added here (see point 8. and 9.)
    
            private static string Get(string id) =>
                resourceManager.GetString(id, LanguageSettings.DisplayLanguage) ?? String.Format(unavailableResource, id);
    
            private static string Get(string id, params object[] args)
            {
                string format = Get(id);
                return args == null || args.Length == 0 ? format : SafeFormat(format, args);
            }
    
            private static string SafeFormat(string format, object[] args)
            {
                try
                {
                    int i = Array.IndexOf(args, null);
                    if (i >= 0)
                    {
                        string nullRef = Get(NullReference);
                        for (; i < args.Length; i++)
                        {
                            if (args[i] == null)
                                args[i] = nullRef;
                        }
                    }
    
                    return String.Format(LanguageSettings.FormattingLanguage, format, args);
                }
                catch (FormatException)
                {
                    return String.Format(invalidResource, args.Length, format);
                }
            }
        }
    }
  4. In Solution Explorer right click on ClassLibrary1, then select Add, New Item, Resources File.
    New Resources file
  5. To make sure the compiled name of the resource is what you want (it must match the name in the DynamicResourceManager constructor) you can edit the .csproj file as follows:
    • In Solution Explorer right click on ClassLibrary1, Unload Project
    • In Solution Explorer right click on ClassLibrary1 (unavailable), Edit ClassLibrary1.csproj
    • Search for the EmbeddedResource element and edit it as follows (or in case of a .NET Core project add it if it does not exist):
      XML
      <!-- .NET Framework project: -->
      <EmbeddedResource Include="Resource1.resx" >
        <LogicalName>ClassLibrary1.Messages.resources</LogicalName>
      </EmbeddedResource>
      
      <!-- .NET Core project (note "Update" in place of "Include"): -->
      <EmbeddedResource Update="Resource1.resx" >
        <LogicalName>ClassLibrary1.Messages.resources</LogicalName>
      </EmbeddedResource>
    • In Solution Explorer right click on ClassLibrary1 (unavailable), Reload ClassLibrary1.csproj
  6. In Solution Explorer right click on the new resource file (Resource1.resx) and select Properties
  7. Clear the default Custom Tool value because the generated file uses a ResourceManager class internally, which cannot handle the dynamic expansions. It means also, that instead of the generated Resources class we will use our Res class. Leave the Build Action so its value is Embedded Resource, which means that the resource will be compiled into the assembly. The DynamicResourceManager will use these compiled resources as default values. If the consumer application of our class library sets the LanguageSettings.DynamicResourceManagersSource property to CompiledAndResX, then for the different languages the .resx files will be automatically created containing the resource set of our class library, ready to translate.
    Resources1.resx properties
  8. In Solution Explorer double click on Resource1.resx and add any resource entries you want to use in your library. Add NullReference key as well as it is used by the default Res implementation.
    Example resources
  9. Define a property for all of your simple resources and a method for the format strings with placeholders in the Res class. For example:
    C#
    // simple resource: can be a property
    internal static string MyResourceExample => Get(nameof(MyResourceExample));
    
    // resource format string with placeholders: can be a method
    internal static string MyResourceFormatExample(int arg1, string arg2) => Get(nameof(MyResourceFormatExample), arg1, arg2);
  10. You can retrieve any resources in your library as it is shown in the example below:
    C#
    using System;
    
    namespace ClassLibrary1
    {
        public class Example
        {
            public void SomeMethod(int someParameter)
            {
                if (someParameter == 42)
                    // simple resource
                    throw new InvalidOperationException(Res.MyResourceExample);
                if (someParameter == -42)
                    // formatted resource - enough arguments must be specified for placeholders (though errors are handled in Res)
                    throw new ArgumentException(Res.MyResourceFormatExample(123, "x"), nameof(someParameter));
            }
        }
    }
  11. To indicate the language of your default compiled resource open the AssemblyInfo.cs of your project and add the following line:
    C#
    // this informs the resource manager about the language of the default culture
    [assembly:NeutralResourcesLanguage("en")]
  12. Now a consumer application can enable dynamic resource creation for new languages. Create a new console application, add reference to ClassLibrary1 project and edit the Program.cs file as follows:
    C#
    using System;
    using System.Globalization;
    using ClassLibrary1;
    using KGySoft;
    using KGySoft.Resources;
    
    namespace ConsoleApp1
    {
        class Program
        {
            static void Main(string[] args)
            {
                // Enabling dynamic .resx creation for all DynamicResourceManager instances in this application,
                // which are configured to use centralized settings
                LanguageSettings.DynamicResourceManagersSource = ResourceManagerSources.CompiledAndResX;
    
                // Setting the language of the application
                LanguageSettings.DisplayLanguage = CultureInfo.GetCultureInfo("fr-FR");
    
                LaunchMyApplication();
            }
    
            private static void LaunchMyApplication()
            {
                // if we use anything that has a reference to the Res class in ClassLibrary1, then
                // a .resx file with the language of the application will be used or created if not exists yet.
                var example = new Example();
                try
                {
                    example.SomeMethod(42);
                }
                catch (Exception e)
                {
                    Console.WriteLine($"{e.GetType().Name}: {e.Message}");
                }
            }
        }
    }
    When the console application above exits, it creates a Resources\ClassLibrary1.Messages.fr.resx file with the following content:
    XML
    <?xml version="1.0"?>
    <root>
      <data name="NullReference">
        <value>[T]&lt;null&gt;</value>
      </data>
      <data name="MyResourceExample">
        <value>[T]This is a resource value will be used in my library</value>
      </data>
      <data name="MyResourceFormatExample">
        <value>[T]This is a resource format string with two placeholders: {0}, {1}</value>
      </data>
    </root>
    By looking for the '[T]' prefixes you can easily find the untranslated elements.

See Also

Reference