Programmatic way to get all the available languages (in satellite assemblies) - c#

I'm designing a multilingual application using .resx files.
I have a few files like GlobalStrings.resx, GlobalStrings.es.resx, GlobalStrings.en.resx, etc.
When I want to use this, I just need to set Thread.CurrentThread.CurrentCulture.
The problem:
I have a combobox with all the available languages, but I'm loading this manually:
comboLanguage.Items.Add(CultureInfo.GetCultureInfo("en"));
comboLanguage.Items.Add(CultureInfo.GetCultureInfo("es"));
I've tried with
cmbLanguage.Items.AddRange(CultureInfo.GetCultures(CultureTypes.UserCustomCulture));
without any success. Also tried with all the elements in CultureTypes, but I'm only getting a big list with a lot more languages that I'm not using, or an empty list.
Is there any way to get only the supported languages?

You can programatically list the cultures available in your application
// Pass the class name of your resources as a parameter e.g. MyResources for MyResources.resx
ResourceManager rm = new ResourceManager(typeof(MyResources));
CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
foreach (CultureInfo culture in cultures)
{
try
{
ResourceSet rs = rm.GetResourceSet(culture, true, false);
// or ResourceSet rs = rm.GetResourceSet(new CultureInfo(culture.TwoLetterISOLanguageName), true, false);
string isSupported = (rs == null) ? " is not supported" : " is supported";
Console.WriteLine(culture + isSupported);
}
catch (CultureNotFoundException exc)
{
Console.WriteLine(culture + " is not available on the machine or is an invalid culture identifier.");
}
}

based on answer by #hans-holzbart but fixed to not return the InvariantCulture too and wrapped into a reusable method:
public static IEnumerable<CultureInfo> GetAvailableCultures()
{
List<CultureInfo> result = new List<CultureInfo>();
ResourceManager rm = new ResourceManager(typeof(Resources));
CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
foreach (CultureInfo culture in cultures)
{
try
{
if (culture.Equals(CultureInfo.InvariantCulture)) continue; //do not use "==", won't work
ResourceSet rs = rm.GetResourceSet(culture, true, false);
if (rs != null)
result.Add(culture);
}
catch (CultureNotFoundException)
{
//NOP
}
}
return result;
}
using that method, you can get a list of strings to add to some ComboBox with the following:
public static ObservableCollection<string> GetAvailableLanguages()
{
var languages = new ObservableCollection<string>();
var cultures = GetAvailableCultures();
foreach (CultureInfo culture in cultures)
languages.Add(culture.NativeName + " (" + culture.EnglishName + " [" + culture.TwoLetterISOLanguageName + "])");
return languages;
}

This would be one of solution on basis of following statement:
Each satellite assembly for a specific language is named the same but lies in a sub-folder named after the specific culture e.g. fr or fr-CA.
public IEnumerable<CultureInfo> GetSupportedCulture()
{
//Get all culture
CultureInfo[] culture = CultureInfo.GetCultures(CultureTypes.AllCultures);
//Find the location where application installed.
string exeLocation = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path));
//Return all culture for which satellite folder found with culture code.
return culture.Where(cultureInfo => Directory.Exists(Path.Combine(exeLocation, cultureInfo.Name)));
}

I'm not sure about getting the languages, maybe you can scan your installation folder for dll-files, but setting your language to an unsupported language should not be a problem.
.NET will fallback to the culture neutral resources if no culture specific files can be found so you can safely select unsupported languages.
As long as you control the application yourself you could just store the available languages in a application setting somewhere. Just a comma-separated string with the culture names should suffice: "en, es"

Using what Rune Grimstad said I end up with this:
string executablePath = Path.GetDirectoryName(Application.ExecutablePath);
string[] directories = Directory.GetDirectories(executablePath);
foreach (string s in directories)
{
try
{
DirectoryInfo langDirectory = new DirectoryInfo(s);
cmbLanguage.Items.Add(CultureInfo.GetCultureInfo(langDirectory.Name));
}
catch (Exception)
{
}
}
or another way
int pathLenght = executablePath.Length + 1;
foreach (string s in directories)
{
try
{
cmbLanguage.Items.Add(CultureInfo.GetCultureInfo(s.Remove(0, pathLenght)));
}
catch (Exception)
{
}
}
I still don't think that this is a good idea ...

A generic answer where the resource type to search is specified. Uses reflection but is cached.
Usage:
List<string> comboBoxEntries = CommonUtil.CulturesOfResource<GlobalStrings>()
.Select(cultureInfo => cultureInfo.NativeName)
.ToList();
Implementation (Utility Class):
static ConcurrentDictionary<Type, List<CultureInfo>> __resourceCultures = new ConcurrentDictionary<Type, List<CultureInfo>>();
/// <summary>
/// Return the list of cultures that is supported by a Resource Assembly (usually collection of resx files).
/// </summary>
static public List<CultureInfo> CulturesOfResource<T>()
{
return __resourceCultures.GetOrAdd(typeof(T), (t) =>
{
ResourceManager manager = new ResourceManager(t);
return CultureInfo.GetCultures(CultureTypes.AllCultures)
.Where(c => !c.Equals(CultureInfo.InvariantCulture) &&
manager.GetResourceSet(c, true, false) != null)
.ToList();
});
}
It may suffer the same issue with the accepted answer in that all the language resources will probably be loaded.

#"Ankush Madankar" presents an interesting starting point but it has two problems:
1) Finds also resource folders for resources of refrenced assemblies
2) Doesn find the resource for the base assembly language
I won't try to solve issue 2) but for issue 1) the code should be
public List<CultureInfo> GetSupportedCultures()
{
CultureInfo[] culture = CultureInfo.GetCultures(CultureTypes.AllCultures);
// get the assembly
Assembly assembly = Assembly.GetExecutingAssembly();
//Find the location of the assembly
string assemblyLocation =
Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(assembly.CodeBase).Path));
//Find the file anme of the assembly
string resourceFilename = Path.GetFileNameWithoutExtension(assembly.Location) + ".resources.dll";
//Return all culture for which satellite folder found with culture code.
return culture.Where(cultureInfo =>
assemblyLocation != null &&
Directory.Exists(Path.Combine(assemblyLocation, cultureInfo.Name)) &&
File.Exists(Path.Combine(assemblyLocation, cultureInfo.Name, resourceFilename))
).ToList();
}

1. Gets the set of languages that are preferred by the user, in order of preference:
Windows.System.UserProfile.GlobalizationPreferences.Languages;
2. Gets or sets the language qualifier for this context (application):
Windows.ApplicationModel.Resources.Core.ResourceContext.GetForCurrentView().Languages;
Both are List<string>.

Related

Which is the best way to use the Culture Globalization for winforms c#

I want to make my application which supports all the languages based on the resx file.
If not resx file is available it has to take the default language from the display language set in the control panel. How can I do this? Which is the best way to do it?
You can't.
The default language in the control panel is the language that is going to be used automatically (unless you change the UI culture).
However, what do you expect the application to do if the default windows language cannot be found?
You have to create a resource file which do not have the language suffix (just yourResource.resx and not yourResource.fi.resx). The resource files without the prefix will be used if the chosen language is not found.
Can't we do like this:
using System.Xml.Linq;
class ExternalRMStrings
{
public static bool allAtOnce = true;
//static string path = #"C:\WOI\Code\VC Days are here again\Ode to Duty\WinForms\Globalization\MultiLingual\MultiLingual\Resource_Hindi.resx";
//#"d:\Resources.resx";
static string path = #"C:\WOI\Code\VC Days are here again\Ode to Duty\WinForms\Globalization\MultiLingual\MultiLingual\Properties\Resources.resx";
static XElement xelement = XElement.Load(path);
static IEnumerable<XElement> employees = null;
static Dictionary<string, string> dicOfLocalizedStrings = new Dictionary<string, string>();
static void LoadAllAtOnce()
{
if (employees == null) employees = xelement.Elements();
employees.Where(e => e.Name == "data").Select(x => x).All(xele =>
{
dicOfLocalizedStrings[xele.Attribute("name").Value] = xele.Element("value").Value;
return true;
});
}
public static string GetString(string key)
{
if (employees == null) employees = xelement.Elements();
if (allAtOnce) LoadAllAtOnce();
try
{
string sibla = null;
if (dicOfLocalizedStrings.TryGetValue(key, out sibla)) return sibla;
sibla = employees.Where(e => e.Name == "data" && e.Attribute("name").Value == key).Select(x => x.Element("value").Value).FirstOrDefault();
dicOfLocalizedStrings[key] = sibla;
return sibla;
}
catch
{
return null;
}
}
}
Useage
ExternalRMStrings.GetString("MyKey");

C# Get form names of project A from Project B

I have two Projects in one solution, project A and project B (using VS2010 Ultimate and C# windows application).
Project B acts as a user management application for project A.
In project B i have a form that contains a chekcedlistbox control that will list all project A forms names and texts (this form will let system administrator to grant users the forms that are allowed to view/edit based on their security groups)
this is my code:
private void GetFormNames()
{
foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (Type t in a.GetTypes())
{
if (t.BaseType == typeof(Form))
{
var emptyCtor = t.GetConstructor(Type.EmptyTypes);
if (emptyCtor != null)
{
var f = (Form)emptyCtor.Invoke(new object[] { });
string FormText = f.Text;
string FormName = f.Name;
checkedListBox1.Items.Add("" + FormText + "//" + FormName + "");
}
}
}
}
}
the result i am getting is the form names of my current project (B) and Empty lines(//) and Select Window//MdiWindowDialog, PrintPreview.
I'm going to assume you've referenced ProjectA correctly and all the forms you're interested in actually have a public parameterless constructor. The problem is likely caused by ProjectA not being loaded yet, you can fix this in multiple ways. Probably the most direct is to use the static Assembly.Load (as long as the files are in the same directory, if not it gets more complicated).
try
{
Assembly projectA = Assembly.Load("ProjectA"); // replace with actual ProjectA name
// despite all Microsoft's dire warnings about loading from a simple name,
// you should be fine here as long as you don't have multiple versions of ProjectA
// floating around
foreach (Type t in projectA.GetTypes())
{
if (t.BaseType == typeof(Form))
{
var emptyCtor = t.GetConstructor(Type.EmptyTypes);
if (emptyCtor != null)
{
var f = (Form)emptyCtor.Invoke(new object[] { });
// t.FullName will help distinguish the unwanted entries and
// possibly later ignore them
string formItem = t.FullName + " // " + f.Text + " // " + f.Name;
checkedListBox1.Items.Add(formItem);
}
}
}
}
catch(Exception err)
{
// log exception
}
Another (probably cleaner solution) would be to have all the forms you're interested in inherit from a single base form. You could then load the assembly from that known Type and check that each enumerated Type inherits from it before adding it to your list. This is a more extensive change, however, and touches ProjectA.
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly a in assemblies)
{
Type[] types = a.GetTypes();
foreach (Type t in types)
{
if (t.BaseType == typeof(Form))
{
//Do Your works
}
}
}
Try This Code:
private void GetFormNames()
{
Type[] AllTypesInProjects = Assembly.GetExecutingAssembly().GetTypes();
for (int i = 0; i < AllTypesInProjects.Length; i++)
{
if (AllTypesInProjects[i].BaseType == typeof(Form))
{ /* Convert Type to Object */
Form f = (Form)Activator.CreateInstance(AllTypesInProjects[i]);
string FormText = f.Text;
listBox1.Items.Add(FormText);
}
}
}

IN c# how to get file names starting with a prefix from resource folder

how to access a text file based on its prefix
var str = GrvGeneral.Properties.Resources.ResourceManager.GetString(configFile + "_Nlog_Config");
var str1 = GrvGeneral.Properties.Resources.ResourceManager.GetObject(configFile + "_Nlog_Config");
where the configfile is the prefix of the resourcefile A & B .
Based on the configfile contents (prefix) the resource file A & B has to be accessed .
Use the DirectoryInfo class (documentation). Then you can call the GetFiles with a search pattern.
string searchPattern = "abc*.*"; // This would be for you to construct your prefix
DirectoryInfo di = new DirectoryInfo(#"C:\Path\To\Your\Dir");
FileInfo[] files = di.GetFiles(searchPattern);
Edit: If you have a way of constructing the actual file name you're looking for, you can go directly to the FileInfo class, otherwise you'll have to iterate through the matching files in my previous example.
Your question is rather vague...but it sounds like you want to get the text contents of an embedded resource. Usually you would do that using Assembly.GetManifestResourceStream. You can always use LINQ along with Assembly.GetManifestResourceNames() to find the name of an embedded file matching a pattern.
The ResourceManager class is more often used for automatically retrieving localized string resources, such as labels and error messages in different languages.
update: A more generalized example:
internal static class RsrcUtil {
private static Assembly _thisAssembly;
private static Assembly thisAssembly {
get {
if (_thisAssembly == null) { _thisAssembly = typeof(RsrcUtil).Assembly; }
return _thisAssembly;
}
}
internal static string GetNlogConfig(string prefix) {
return GetResourceText(#"Some\Folder\" + prefix + ".nlog.config");
}
internal static string FindResource(string pattern) {
return thisAssembly.GetManifestResourceNames()
.FirstOrDefault(x => Regex.IsMatch(x, pattern));
}
internal static string GetResourceText(string resourceName) {
string result = string.Empty;
if (thisAssembly.GetManifestResourceInfo(resourceName) != null) {
using (Stream stream = thisAssembly.GetManifestResourceStream(resourceName)) {
result = new StreamReader(stream).ReadToEnd();
}
}
return result;
}
}
Using the example:
string aconfig = RsrcUtil.GetNlogConfig("a");
string bconfigname = RsrcUtil.FindResource(#"b\.\w+\.config$");
string bconfig = RsrcUtil.GetResourceText(bconfigname);

C# CultureInfo.GetCultures returns an empty list

I cannot use GetCultures, from what I can tell it returns a blank list.
private void AddressChooser_Load(object sender, EventArgs e)
{
MessageBox.Show("Form load event successfully triggered") //Debug message - This appears at runtime
foreach (string country in GetCountryList())
{
MessageBox.Show(country); //Debug message - This does not appear at runtime!!
countryBox.Items.Clear();
countryBox.Items.Add(country);
}
}
public static List<string> GetCountryList()
{
MessageBox.Show("Function has been triggered successfully"); //Debug message - This appears at runtime
List<string> cultureList = new List<string>();
CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.AllCultures & ~CultureTypes.NeutralCultures);
foreach (CultureInfo culture in cultures)
{
RegionInfo region = new RegionInfo(culture.LCID);
if (!(cultureList.Contains(region.EnglishName)))
cultureList.Add(region.EnglishName);
MessageBox.Show(region.EnglishName); //Debug message - This does not appear at runtime!
}
return cultureList;
}
I find it strange that this doesn't work considering it is simply a copy&pasted snippet.
Please help!
Thanks
You must be sweeping an Exception under the floormat somewhere.
Your code fails because CultureTypes.AllCultures & ~CultureTypes.NeutralCultures doesn't work. Your list contains neutral cultures and new RegionInfo() throws.
The snippet:
var c1 = CultureTypes.AllCultures & ~CultureTypes.NeutralCultures;
Console.WriteLine(c1);
produces SpecificCultures, InstalledWin32Cultures and I suppose those Win32 cultures contain neutral ones.
The simple solution would be CultureInfo.GetCultures(CultureTypes.SpecificCultures);
But the main thing to fix would be your Exception handling and debugging techniques.
Firstly it is a good idea to Debug>>Exceptions and set the CLR Runtime Execptions to Thrown . Secondly I think this code can be what you are looking for:
static void Main()
{
List<string> cultureList = new List<string>();
CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.AllCultures & ~CultureTypes.NeutralCultures);
foreach (CultureInfo culture in cultures)
{
try
{
RegionInfo region = new RegionInfo(culture.Name);
if (!(cultureList.Contains(region.EnglishName)))
cultureList.Add(region.EnglishName);
Console.WriteLine(region.EnglishName);
}
catch (ArgumentException e)
{
Console.WriteLine(String.Format("For{0} a specific culture name is required.", culture.Name));
}
}
}
Use a bitwise or instead of and here CultureInfo.GetCultures(CultureTypes.AllCultures & ~CultureTypes.NeutralCultures);

Get all the supported Cultures from a satellite assembly

I am using a satellite assembly to hold all the localization resources in a C# application.
What I need to do is create a menu in the GUI with all the available languages that exists for the application. Is there any way to get information dynamically?
This function returns an array of all the installed cultures in the App_GlobalResources folder - change search path according to your needs.
For the invariant culture it returns "auto".
public static string[] GetInstalledCultures()
{
List<string> cultures = new List<string>();
foreach (string file in Directory.GetFiles(HttpContext.Current.Server.MapPath("/App_GlobalResources"), \\Change folder to search in if needed.
"*.resx", SearchOption.TopDirectoryOnly))
{
string name = file.Split('\\').Last();
name = name.Split('.')[1];
cultures.Add(name != "resx" ? name : "auto"); \\Change "auto" to something else like "en-US" if needed.
}
return cultures.ToArray();
}
You could also use this one for more functionality getting the full CultureInfo instances:
public static CultureInfo[] GetInstalledCultures()
{
List<CultureInfo> cultures = new List<CultureInfo>();
foreach (string file in Directory.GetFiles(HttpContext.Current.Server.MapPath("/App_GlobalResources"), "*.resx", SearchOption.TopDirectoryOnly))
{
string name = file.Split('\\').Last();
name = name.Split('.')[1];
string culture = name != "resx" ? name : "en-US";
cultures.Add(new CultureInfo(culture));
}
return cultures.ToArray();
}
Each satellite assembly for a specific language is named the same but lies in a sub-folder named after the specific culture e.g. fr or fr-CA.
Maybe you can use this fact and scan the folder hierarchy to build up that menu dynamically.

Categories

Resources