Programmatic way to get all the available languages (in satellite assemblies)
Asked Answered
M

8

34

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?

Murrah answered 16/2, 2009 at 13:7 Comment(0)
M
2

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

Murrah answered 16/2, 2009 at 21:28 Comment(2)
I think this is the only solution we got. I'm in the same boat.Swear
Wouldn't this also get the resource directories that your dependencies deployed (if any)? So you might end up with some cultures you don't support but some of your dependencies do.Accountancy
P
55

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.");
    }
}
Peasant answered 15/6, 2012 at 9:21 Comment(4)
Note that GlobalStrings.resx will be detected under InvariantCulture.Dieterich
Won't this essentially force load all satellite assemblies?Allotment
@TaylorBuchanan could probably call ReleaseAllResources after that (msdn.microsoft.com/en-us/library/…) and then load the specific resource againGlochidiate
You must exclude language invariant resources if (resourceSet != null && !string.IsNullOrWhiteSpace(culture.ToString())) ....Everyday
G
6

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;
}
Glochidiate answered 22/8, 2015 at 22:41 Comment(0)
S
4

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)));
}
Scepter answered 6/10, 2015 at 6:54 Comment(3)
This solution works on my ASP.net MVC project. Thank you !!Fourflush
@JeffT. Yes, it is much generic in way, I have use this approach for our WPF project!Scepter
Please note that this will also return the invariant language.Prosperus
Z
2

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"

Zawde answered 16/2, 2009 at 13:12 Comment(0)
M
2

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

Murrah answered 16/2, 2009 at 21:28 Comment(2)
I think this is the only solution we got. I'm in the same boat.Swear
Wouldn't this also get the resource directories that your dependencies deployed (if any)? So you might end up with some cultures you don't support but some of your dependencies do.Accountancy
M
1

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.

Mountebank answered 28/8, 2019 at 1:20 Comment(2)
And what is type of GlobalStrings?Hemostat
Its been awhile but I believe that type can be any public type that is provided as a resource (possibly in a different assembly). Another example could be (makes more sense fully qualified): MC.Server.SomeLocalizableResourcesNamespace.ResourceMountebank
E
0

@"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();
}
Everyday answered 5/5, 2018 at 12:12 Comment(0)
F
-1

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

Flog answered 18/3, 2021 at 0:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.