Recuperación de recursos en aplicaciones .NET

Al trabajar con recursos localizados en aplicaciones .NET, en principio se deberían empaquetar los recursos de la referencia cultural predeterminada o neutra con el ensamblado principal y, luego, crear un ensamblado satélite independiente para todos los idiomas o referencias culturales que admita la aplicación. Después podrá usar la clase ResourceManager como se describe en la siguiente sección para obtener acceso a los recursos con nombre. Si opta por no insertar los recursos en el ensamblado principal y los ensamblados satélite, también puede obtener acceso directamente a los archivos .resources binarios, como se describe en la sección Recuperación de recursos desde archivos .resources más adelante en este artículo.

Recuperación de recursos de ensamblados

La clase ResourceManager proporciona acceso a los recursos en tiempo de ejecución. Puede usar el método ResourceManager.GetString para recuperar recursos de cadena y el método ResourceManager.GetObject o ResourceManager.GetStream para recuperar recursos que no son de cadena. Cada método tiene dos sobrecargas:

  • Una sobrecarga cuyo parámetro único es una cadena que contiene el nombre del recurso. El método intenta recuperar ese recurso para la referencia cultural actual. Para obtener más información, consulte los métodos GetString(String), GetObject(String)y GetStream(String) .

  • Una sobrecarga que tiene dos parámetros: una cadena que contiene el nombre del recurso y un objeto CultureInfo que representa la referencia cultural cuyo recurso se debe recuperar. Si no se encuentra un recurso establecido para esa referencia cultural, el Administrador de recursos usa reglas de reserva para recuperar un recurso adecuado. Para obtener más información, consulte los métodos GetString(String, CultureInfo), GetObject(String, CultureInfo)y GetStream(String, CultureInfo) .

El Administrador de recursos usa el proceso de reserva de recursos para controlar la forma en que la aplicación recupera los recursos específicos de referencia cultural. Para obtener más información, consulte la sección "Proceso de reserva de recursos" de Empaquetar e implementar recursos. Para obtener información sobre cómo crear una instancia de un objeto ResourceManager , consulte la sección "Instantiating a ResourceManager Object" (Crear una instancia de un objeto ResourceManager) del tema de la clase ResourceManager .

Ejemplo de recuperación de datos de cadena

En el ejemplo siguiente se llama al método GetString(String) para recuperar los recursos de cadena de la referencia cultural de interfaz de usuario actual. Incluye un recurso de cadena neutro para la referencia cultural de inglés (Estados Unidos) y recursos localizados para las referencias culturales de ruso (Rusia) y de francés (Francia). El siguiente recurso de inglés (Estados Unidos) está en un archivo denominado Strings.txt:

TimeHeader=The current time is

El recurso de francés (Francia) está en un archivo denominado Strings.fr-FR.txt:

TimeHeader=L'heure actuelle est

El recurso de ruso (Rusia) está en un archivo denominado Strings.ru-RU.txt:

TimeHeader=Текущее время —

El código fuente de este ejemplo, que está en un archivo denominado GetString.cs para la versión de C# del código y GetString.vb para la versión de Visual Basic, define una matriz de cadenas que contiene el nombre de cuatro referencias culturales: las tres referencias culturales para las que los recursos están disponibles y la referencia cultural de español (España). Un bucle que se ejecuta cinco veces de forma aleatoria selecciona una de estas referencias culturales y la asigna a las propiedades Thread.CurrentCulture y CultureInfo.CurrentUICulture . Luego, llama al método GetString(String) para recuperar la cadena localizada, que aparecerá junto con la hora del día.

using System;
using System.Globalization;
using System.Resources;
using System.Threading;

[assembly: NeutralResourcesLanguageAttribute("en-US")]

public class Example
{
   public static void Main()
   {
      string[] cultureNames = { "en-US", "fr-FR", "ru-RU", "es-ES" };
      Random rnd = new Random();
      ResourceManager rm = new ResourceManager("Strings",
                               typeof(Example).Assembly);

      for (int ctr = 0; ctr <= cultureNames.Length; ctr++) {
         string cultureName = cultureNames[rnd.Next(0, cultureNames.Length)];
         CultureInfo culture = CultureInfo.CreateSpecificCulture(cultureName);
         Thread.CurrentThread.CurrentCulture = culture;
         Thread.CurrentThread.CurrentUICulture = culture;

         Console.WriteLine("Current culture: {0}", culture.NativeName);
         string timeString = rm.GetString("TimeHeader");
         Console.WriteLine("{0} {1:T}\n", timeString, DateTime.Now);
      }
   }
}
// The example displays output like the following:
//    Current culture: English (United States)
//    The current time is 9:34:18 AM
//
//    Current culture: Español (España, alfabetización internacional)
//    The current time is 9:34:18
//
//    Current culture: русский (Россия)
//    Текущее время — 9:34:18
//
//    Current culture: français (France)
//    L'heure actuelle est 09:34:18
//
//    Current culture: русский (Россия)
//    Текущее время — 9:34:18
Imports System.Globalization
Imports System.Resources
Imports System.Threading

<Assembly: NeutralResourcesLanguageAttribute("en-US")>

Module Example
    Public Sub Main()
        Dim cultureNames() As String = {"en-US", "fr-FR", "ru-RU", "es-ES"}
        Dim rnd As New Random()
        Dim rm As New ResourceManager("Strings", GetType(Example).Assembly)

        For ctr As Integer = 0 To cultureNames.Length
            Dim cultureName As String = cultureNames(rnd.Next(0, cultureNames.Length))
            Dim culture As CultureInfo = CultureInfo.CreateSpecificCulture(cultureName)
            Thread.CurrentThread.CurrentCulture = culture
            Thread.CurrentThread.CurrentUICulture = culture

            Console.WriteLine("Current culture: {0}", culture.NativeName)
            Dim timeString As String = rm.GetString("TimeHeader")
            Console.WriteLine("{0} {1:T}", timeString, Date.Now)
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays output similar to the following:
'    Current culture: English (United States)
'    The current time is 9:34:18 AM
'    
'    Current culture: Español (España, alfabetización internacional)
'    The current time is 9:34:18
'    
'    Current culture: русский (Россия)
'    Текущее время — 9:34:18
'    
'    Current culture: français (France)
'    L'heure actuelle est 09:34:18
'    
'    Current culture: русский (Россия)
'    Текущее время — 9:34:18

El archivo por lotes ( .bat) a continuación compila el ejemplo y genera los ensamblados satélite en los directorios correspondientes. Los comandos se proporcionan para el lenguaje C# y para el compilador. Si usa Visual Basic, cambie csc por vbcy GetString.cs por GetString.vb.

resgen strings.txt
csc GetString.cs -resource:strings.resources

resgen strings.fr-FR.txt
md fr-FR
al -embed:strings.fr-FR.resources -culture:fr-FR -out:fr-FR\GetString.resources.dll

resgen strings.ru-RU.txt
md ru-RU
al -embed:strings.ru-RU.resources -culture:ru-RU -out:ru-RU\GetString.resources.dll

Si la referencia cultural de interfaz de usuario actual es el español (España), tenga en cuenta que en el ejemplo se muestran los recursos del idioma inglés, ya que los recursos del idioma español no están disponibles y el inglés es la referencia cultural predeterminada del ejemplo.

Ejemplos de recuperación de datos de objeto

Puede usar los métodos GetObject y GetStream para recuperar datos de objeto. Se incluyen los tipos de datos primitivos, los objetos serializables y los objetos que se almacenan en formato binario (como las imágenes).

En el ejemplo siguiente se usa el método GetStream(String) para recuperar un mapa de bits que se emplea en la ventana de inicio de una aplicación. El siguiente código fuente del archivo CreateResources.cs (para C#) o CreateResources.vb (para Visual Basic) genera un archivo .resx que contiene la imagen serializada. En este caso, la imagen se carga desde un archivo denominado SplashScreen.jpg; puede modificar el nombre del archivo para sustituir su propia imagen.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Resources;

public class Example
{
   public static void Main()
   {
      Bitmap bmp = new Bitmap(@".\SplashScreen.jpg");
      MemoryStream imageStream = new MemoryStream();
      bmp.Save(imageStream, ImageFormat.Jpeg);

      ResXResourceWriter writer = new ResXResourceWriter("AppResources.resx");
      writer.AddResource("SplashScreen", imageStream);
      writer.Generate();
      writer.Close();
   }
}
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Resources

Module Example
    Public Sub Main()
        Dim bmp As New Bitmap(".\SplashScreen.jpg")
        Dim imageStream As New MemoryStream()
        bmp.Save(imageStream, ImageFormat.Jpeg)

        Dim writer As New ResXResourceWriter("AppResources.resx")
        writer.AddResource("SplashScreen", imageStream)
        writer.Generate()
        writer.Close()
    End Sub
End Module

El siguiente código recupera el recurso y muestra la imagen en un control PictureBox .

using System;
using System.Drawing;
using System.IO;
using System.Resources;
using System.Windows.Forms;

public class Example
{
   public static void Main()
   {
      ResourceManager rm = new ResourceManager("AppResources", typeof(Example).Assembly);
      Bitmap screen = (Bitmap) Image.FromStream(rm.GetStream("SplashScreen"));

      Form frm = new Form();
      frm.Size = new Size(300, 300);

      PictureBox pic = new PictureBox();
      pic.Bounds = frm.RestoreBounds;
      pic.BorderStyle = BorderStyle.Fixed3D;
      pic.Image = screen;
      pic.SizeMode = PictureBoxSizeMode.StretchImage;

      frm.Controls.Add(pic);
      pic.Anchor = AnchorStyles.Top | AnchorStyles.Bottom |
                   AnchorStyles.Left | AnchorStyles.Right;

      frm.ShowDialog();
   }
}
Imports System.Drawing
Imports System.IO
Imports System.Resources
Imports System.Windows.Forms

Module Example
    Public Sub Main()
        Dim rm As New ResourceManager("AppResources", GetType(Example).Assembly)
        Dim screen As Bitmap = CType(Image.FromStream(rm.GetStream("SplashScreen")), Bitmap)

        Dim frm As New Form()
        frm.Size = new Size(300, 300)

        Dim pic As New PictureBox()
        pic.Bounds = frm.RestoreBounds
        pic.BorderStyle = BorderStyle.Fixed3D
        pic.Image = screen
        pic.SizeMode = PictureBoxSizeMode.StretchImage

        frm.Controls.Add(pic)
        pic.Anchor = AnchorStyles.Top Or AnchorStyles.Bottom Or
                     AnchorStyles.Left Or AnchorStyles.Right

        frm.ShowDialog()
    End Sub
End Module

Puede usar el siguiente archivo por lotes para compilar el ejemplo de C#. Si usa Visual Basic, cambie csc por vbcy la extensión del archivo de código fuente de .cs a .vb.

csc CreateResources.cs
CreateResources

resgen AppResources.resx

csc GetStream.cs -resource:AppResources.resources

En el siguiente ejemplo se usa el método ResourceManager.GetObject(String) para deserializar un objeto personalizado. El ejemplo incluye un archivo de código fuente denominado UIElements.cs (UIElements.vb en el caso de Visual Basic) que define la siguiente estructura, denominada PersonTable. Esta estructura está pensada para usarse con una rutina de visualización general de tabla en la que se muestren los nombres localizados de las columnas de la tabla. Tenga en cuenta que la estructura PersonTable está marcada con el atributo SerializableAttribute .

using System;

[Serializable] public struct PersonTable
{
   public readonly int nColumns;
   public readonly string column1;
   public readonly string column2;
   public readonly string column3;
   public readonly int width1;
   public readonly int width2;
   public readonly int width3;

   public PersonTable(string column1, string column2, string column3,
                  int width1, int width2, int width3)
   {
      this.column1 = column1;
      this.column2 = column2;
      this.column3 = column3;
      this.width1 = width1;
      this.width2 = width2;
      this.width3 = width3;
      this.nColumns = typeof(PersonTable).GetFields().Length / 2;
   }
}
<Serializable> Public Structure PersonTable
    Public ReadOnly nColumns As Integer
    Public Readonly column1 As String
    Public ReadOnly column2 As String
    Public ReadOnly column3 As String
    Public ReadOnly width1 As Integer
    Public ReadOnly width2 As Integer
    Public ReadOnly width3 As Integer

    Public Sub New(column1 As String, column2 As String, column3 As String,
                   width1 As Integer, width2 As Integer, width3 As Integer)
        Me.column1 = column1
        Me.column2 = column2
        Me.column3 = column3
        Me.width1 = width1
        Me.width2 = width2
        Me.width3 = width3
        Me.nColumns = Me.GetType().GetFields().Count \ 2
    End Sub
End Structure

El siguiente código del archivo CreateResources.cs (CreateResources.vb para Visual Basic) crea un archivo de recursos XML denominado UIResources.resx que almacena un título de tabla y un objeto PersonTable que contiene información de una aplicación localizada para el idioma inglés.

using System;
using System.Resources;

public class CreateResource
{
   public static void Main()
   {
      PersonTable table = new PersonTable("Name", "Employee Number",
                                          "Age", 30, 18, 5);
      ResXResourceWriter rr = new ResXResourceWriter(@".\UIResources.resx");
      rr.AddResource("TableName", "Employees of Acme Corporation");
      rr.AddResource("Employees", table);
      rr.Generate();
      rr.Close();
   }
}
Imports System.Resources

Module CreateResource
    Public Sub Main()
        Dim table As New PersonTable("Name", "Employee Number", "Age", 30, 18, 5)
        Dim rr As New ResXResourceWriter(".\UIResources.resx")
        rr.AddResource("TableName", "Employees of Acme Corporation")
        rr.AddResource("Employees", table)
        rr.Generate()
        rr.Close()
    End Sub
End Module

Después, el siguiente código del archivo de código fuente GetObject.cs (GetObject.vb) recupera los recursos y los muestra en la consola.

using System;
using System.Resources;

[assembly: NeutralResourcesLanguageAttribute("en")]

public class Example
{
   public static void Main()
   {
      string fmtString = String.Empty;
      ResourceManager rm = new ResourceManager("UIResources", typeof(Example).Assembly);
      string title = rm.GetString("TableName");
      PersonTable tableInfo = (PersonTable) rm.GetObject("Employees");

      if (! String.IsNullOrEmpty(title)) {
         fmtString = "{0," + ((Console.WindowWidth + title.Length) / 2).ToString() + "}";
         Console.WriteLine(fmtString, title);
         Console.WriteLine();
      }

      for (int ctr = 1; ctr <= tableInfo.nColumns; ctr++) {
         string columnName = "column"  + ctr.ToString();
         string widthName = "width" + ctr.ToString();
         string value = tableInfo.GetType().GetField(columnName).GetValue(tableInfo).ToString();
         int width = (int) tableInfo.GetType().GetField(widthName).GetValue(tableInfo);
         fmtString = "{0,-" + width.ToString() + "}";
         Console.Write(fmtString, value);
      }
      Console.WriteLine();
   }
}
Imports System.Resources

<Assembly: NeutralResourcesLanguageAttribute("en")>

Module Example
    Public Sub Main()
        Dim fmtString As String = String.Empty
        Dim rm As New ResourceManager("UIResources", GetType(Example).Assembly)
        Dim title As String = rm.GetString("TableName")
        Dim tableInfo As PersonTable = DirectCast(rm.GetObject("Employees"), PersonTable)

        If Not String.IsNullOrEmpty(title) Then
            fmtString = "{0," + ((Console.WindowWidth + title.Length) \ 2).ToString() + "}"
            Console.WriteLine(fmtString, title)
            Console.WriteLine()
        End If

        For ctr As Integer = 1 To tableInfo.nColumns
            Dim columnName As String = "column" + ctr.ToString()
            Dim widthName As String = "width" + ctr.ToString()
            Dim value As String = CStr(tableInfo.GetType().GetField(columnName).GetValue(tableInfo))
            Dim width As Integer = CInt(tableInfo.GetType().GetField(widthName).GetValue(tableInfo))
            fmtString = "{0,-" + width.ToString() + "}"
            Console.Write(fmtString, value)
        Next
        Console.WriteLine()
    End Sub
End Module

Puede crear el archivo de recursos y los ensamblados necesarios y ejecutar la aplicación con el siguiente archivo por lotes. Debe usar la opción /r para proporcionar a Resgen.exe una referencia a UIElements.dll para que pueda tener acceso a la información de la estructura PersonTable . Si usa C#, reemplace el nombre del compilador vbc por cscy la extensión .vb por .cs.

vbc -t:library UIElements.vb
vbc CreateResources.vb -r:UIElements.dll
CreateResources

resgen UIResources.resx  -r:UIElements.dll
vbc GetObject.vb -r:UIElements.dll -resource:UIResources.resources

GetObject.exe

Compatibilidad de versiones de los ensamblados satélite

De forma predeterminada, cuando el objeto ResourceManager recupera los recursos solicitados, busca ensamblados satélite que tengan números de versión que coincidan con el número de versión del ensamblado principal. Después de implementar una aplicación, puede actualizar el ensamblado principal o los ensamblados satélite de recursos específicos. .NET Framework proporciona compatibilidad de las versiones del ensamblado principal y de los ensamblados satélite.

El atributo SatelliteContractVersionAttribute proporciona compatibilidad de versiones para un ensamblado principal. Al especificar este atributo en el ensamblado principal de una aplicación, puede actualizar y volver a implementar un ensamblado principal sin tener que actualizar sus ensamblados satélite. Una vez actualizado el ensamblado principal, incremente el número de versión del ensamblado principal sin modificar el número de versión del contrato satélite. Cuando el Administrador de recursos recupere los recursos solicitados, cargará la versión del ensamblado satélite especificada por este atributo.

Los ensamblados de directiva de edición ofrecen compatibilidad de las versiones de los ensamblados satélite. Puede actualizar y volver a implementar un ensamblado satélite sin tener que actualizar el ensamblado principal. Después de actualizar un ensamblado satélite, incremente su número de versión y distribúyalo con un ensamblado de directiva de edición. En el ensamblado de directiva de edición, especifique que el nuevo ensamblado satélite es compatible con su versión anterior. El Administrador de recursos usará el atributo SatelliteContractVersionAttribute para determinar la versión del ensamblado satélite, pero el cargador de ensamblados se enlazará con la versión del ensamblado satélite especificada por la directiva de edición. Para obtener más información sobre los ensamblados de directiva de edición, consulte Creación de un archivo de directiva de edición.

Para habilitar la compatibilidad completa de versiones de ensamblados, se recomienda implementar ensamblados con nombres seguros en la caché global de ensamblados e implementar los ensamblados que no tengan nombres seguros en el directorio de la aplicación. Si quiere implementar ensamblados con nombres seguros en el directorio de la aplicación, no podrá aumentar el número de versión de un ensamblado satélite al actualizar el ensamblado. En lugar de ello, debe llevar a cabo una actualización local en la que deberá reemplazar el código existente por el código actualizado y conservar el mismo número de versión. Por ejemplo, si quiere actualizar la versión 1.0.0.0 de un ensamblado satélite con el nombre de ensamblado especificado por completo "MyApp.resources, Version=1.0.0.0, Culture=de, PublicKeyToken=b03f5f11d50a3a", sobrescríbalo con el archivo actualizado myApp.resources.dll que se ha compilado con el mismo nombre de ensamblado especificado por completo "myApp.resources, Version=1.0.0.0, Culture=de, PublicKeyToken=b03f5f11d50a3a". Tenga en cuenta que el uso de actualizaciones locales en los archivos del ensamblado satélite dificulta que una aplicación pueda determinar con exactitud la versión de un ensamblado satélite.

Para obtener más información sobre las versiones de los ensamblados, consulte Versiones de los ensamblados y Cómo el motor en tiempo de ejecución ubica ensamblados.

Recuperación de recursos desde archivos .resources

Si decide no implementar recursos en los ensamblados satélite, puede usar un objeto ResourceManager para obtener acceso directo a los recursos de los archivos .resources. Para ello, debe implementar correctamente los archivos .resources. Luego, deberá usar el método ResourceManager.CreateFileBasedResourceManager para crear una instancia de un objeto ResourceManager y especificar el directorio que contiene los archivos .resources independientes.

Implementación de archivos .resources

Al insertar archivos .resources en un ensamblado de aplicación y en ensamblados satélite, todos los ensamblados satélite tienen el mismo nombre de archivo, pero se encuentran en un subdirectorio que refleja la referencia cultural del ensamblado satélite. En cambio, al obtener acceso directo a los recursos desde los archivos .resources, puede colocar todos los archivos .resources en un solo directorio, que normalmente es un subdirectorio del directorio de la aplicación. El nombre del archivo .resources predeterminado de la aplicación consta solo de un nombre de raíz, sin ninguna indicación de la referencia cultural (por ejemplo, strings.resources). Los recursos de cada referencia cultural localizada se almacenan en un archivo cuyo nombre consta del nombre de raíz seguido de la referencia cultural (por ejemplo, strings.ja.resources o strings.de-DE.resources).

En la siguiente ilustración se muestra dónde deben estar ubicados los archivos de recursos en la estructura de directorio. También proporciona las convenciones de nomenclatura de los archivos .resource.

Ilustración en la que se muestra el directorio principal de la aplicación.

Uso del administrador de recursos

Una vez creados los recursos y colocados en el directorio correspondiente, debe crear un objeto ResourceManager para usar los recursos llamando al método CreateFileBasedResourceManager(String, String, Type) . El primer parámetro especifica el nombre de la raíz del archivo .resources predeterminado de la aplicación (en el ejemplo de la sección anterior sería "strings"). El segundo parámetro especifica la ubicación de los recursos (en el ejemplo anterior, "Resources"). El tercer parámetro especifica la implementación ResourceSet que se va a usar. Si el tercer parámetro es null, se usará la clase ResourceSet en tiempo de ejecución predeterminada.

Nota

No implemente ninguna aplicación ASP.NET con archivos .resources independientes, ya que se pueden producir problemas de bloqueo y se puede interrumpir la implementación de XCOPY. Se recomienda implementar los recursos ASP.NET en ensamblados satélite. Para obtener más información, consulta ASP.NET Web Page Resources Overview.

Después de crear una instancia del objeto ResourceManager , debe usar los métodos GetString, GetObjecty GetStream como se ha descrito anteriormente para recuperar los recursos. A pesar de ello, la recuperación de recursos directamente desde los archivos .resources difiere de la recuperación de recursos insertados desde ensamblados. Al recuperar recursos desde archivos .resources, los métodos GetString(String), GetObject(String)y GetStream(String) siempre recuperan los recursos de la referencia cultural predeterminada, independientemente de la referencia cultural actual. Para recuperar los recursos de la referencia cultural actual de la aplicación o de una referencia cultural en concreto, debe llamar a lo métodos GetString(String, CultureInfo), GetObject(String, CultureInfo) o GetStream(String, CultureInfo) y especificar la referencia cultural cuyos recursos se van a recuperar. Para recuperar los recursos de la referencia cultural actual, especifique el valor de la propiedad CultureInfo.CurrentCulture como argumento culture . Si el Administrador de recursos no puede recuperar los recursos de culture, aplicará las reglas de reserva de recursos estándar para recuperar los recursos adecuados.

Un ejemplo

En el ejemplo siguiente se muestra cómo el Administrador de recursos recupera los recursos directamente desde archivos .resources. El ejemplo consta de tres archivos de recursos basados en texto para las referencias culturales de inglés (Estados Unidos), francés (Francia) y ruso (Rusia). Inglés (Estados Unidos) es la referencia cultural predeterminada del ejemplo. Sus recursos están almacenados en el archivo siguiente, denominado Strings.txt:

Greeting=Hello
Prompt=What is your name?

Los recursos de la referencia cultural de francés (Francia) están almacenados en el siguiente archivo, que se denomina Strings.fr-FR.txt:

Greeting=Bon jour
Prompt=Comment vous appelez-vous?

Los recursos de la referencia cultural de ruso (Rusia) están almacenados en el siguiente archivo, que se denomina Strings.ru-RU.txt:

Greeting=Здравствуйте
Prompt=Как вас зовут?

A continuación se muestra el código fuente del ejemplo. En el ejemplo se crea una instancia de objetos CultureInfo para las referencias culturales de inglés (Estados Unidos), inglés (Canadá), francés (Francia) y ruso (Rusia); además, las convierte a todas en la referencia cultural actual. Luego, el método ResourceManager.GetString(String, CultureInfo) proporciona el valor de la propiedad CultureInfo.CurrentCulture como argumento culture para recuperar los recursos específicos de la referencia cultural adecuados.

using System;
using System.Globalization;
using System.Resources;
using System.Threading;

[assembly: NeutralResourcesLanguage("en-US")]

public class Example
{
   public static void Main()
   {
      string[] cultureNames = { "en-US", "en-CA", "ru-RU", "fr-FR" };
      ResourceManager rm = ResourceManager.CreateFileBasedResourceManager("Strings", "Resources", null);

      foreach (var cultureName in cultureNames) {
         Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName);
         string greeting = rm.GetString("Greeting", CultureInfo.CurrentCulture);
         Console.WriteLine("\n{0}!", greeting);
         Console.Write(rm.GetString("Prompt", CultureInfo.CurrentCulture));
         string name = Console.ReadLine();
         if (! String.IsNullOrEmpty(name))
            Console.WriteLine("{0}, {1}!", greeting, name);
      }
      Console.WriteLine();
   }
}
// The example displays output like the following:
//       Hello!
//       What is your name? Dakota
//       Hello, Dakota!
//
//       Hello!
//       What is your name? Koani
//       Hello, Koani!
//
//       Здравствуйте!
//       Как вас зовут?Samuel
//       Здравствуйте, Samuel!
//
//       Bon jour!
//       Comment vous appelez-vous?Yiska
//       Bon jour, Yiska!
Imports System.Globalization
Imports System.Resources
Imports System.Threading

<Assembly: NeutralResourcesLanguageAttribute("en-US")>

Module Example
    Public Sub Main()
        Dim cultureNames() As String = {"en-US", "en-CA", "ru-RU", "fr-FR"}
        Dim rm As ResourceManager = ResourceManager.CreateFileBasedResourceManager("Strings", "Resources", Nothing)

        For Each cultureName In cultureNames
            Console.WriteLine()
            Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName)
            Dim greeting As String = rm.GetString("Greeting", CultureInfo.CurrentCulture)
            Console.WriteLine("{0}!", greeting)
            Console.Write(rm.GetString("Prompt", CultureInfo.CurrentCulture))
            Dim name As String = Console.ReadLine()
            If Not String.IsNullOrEmpty(name) Then
                Console.WriteLine("{0}, {1}!", greeting, name)
            End If
        Next
        Console.WriteLine()
    End Sub
End Module
' The example displays output like the following:
'       Hello!
'       What is your name? Dakota
'       Hello, Dakota!
'       
'       Hello!
'       What is your name? Koani
'       Hello, Koani!
'       
'       Здравствуйте!
'       Как вас зовут?Samuel
'       Здравствуйте, Samuel!
'       
'       Bon jour!
'       Comment vous appelez-vous?Yiska
'       Bon jour, Yiska!

Puede compilar la versión de C# del ejemplo ejecutando el siguiente archivo por lotes. Si usa Visual Basic, reemplace csc por vbc y la extensión .cs por .vb.

md Resources
resgen Strings.txt Resources\Strings.resources
resgen Strings.fr-FR.txt Resources\Strings.fr-FR.resources
resgen Strings.ru-RU.txt Resources\Strings.ru-RU.resources

csc Example.cs

Vea también