Internacionalización de aplicaciones WinForms/WPF — la práctica de resx, ensamblados satélite y el cambio de cultura

· Actualizado el: · · CSharp, .NET, WinForms, WPF, Internacionalización, Localización, Recursos, UI, Desarrollo de Windows, Consultoría técnica

Historial de revisiones (1 actualizaciones, última el 22 Aug 2026)

Registro de los cambios realizados en este artículo. Cuando se archivó una versión previa, sigue siendo legible mediante un enlace permanente con DOI.

Se ha sustituido la traducción, que estaba abreviada, por una traducción completa del artículo japonés en su versión actual: el texto crece un 20 %. Se han incorporado 2 apartados, 6 filas de tabla y 6 bloques de código que no estaban en la edición anterior. El contenido no cambia respecto al original japonés; esta edición simplemente ya no lo resume. Además, los enlaces a otros artículos que ya tienen edición en español apuntan ahora a esa edición en lugar de a la japonesa. Leer la versión anterior a esta actualización (DOI: 10.5281/zenodo.21638358)
Primera publicación
Citar este artículo(DOI: 10.5281/zenodo.21638357)

Este artículo está archivado en Zenodo. A continuación se muestran tanto el DOI que siempre resuelve a la última versión como el DOI fijado a la versión que está leyendo.

Go Komura (2026). Internacionalización de aplicaciones WinForms/WPF — la práctica de resx, ensamblados satélite y el cambio de cultura. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21638357 https://comcomponent.com/es/blog/winforms-wpf-localization-guide/

DOI (última versión)
10.5281/zenodo.21638357
DOI (esta versión)
10.5281/zenodo.22053466

«Queremos usar la misma aplicación también en las sedes del extranjero», «en la planta ha aumentado el personal que habla otros idiomas, así que queremos que la pantalla también esté en inglés»: como requisito, la internacionalización de una aplicación de escritorio Windows suena sencilla, pero en cuanto se pone manos a la obra aparece una sucesión de puntos que generan dudas: «¿cómo se usa resx?», «WPF tiene varios métodos y no está claro cuál es el correcto», «¿se puede cambiar el idioma mientras la aplicación está en ejecución?».

En este artículo, pensando en aplicaciones de negocio WinForms/WPF, organizamos en el orden en que surgen las dudas en la práctica: la base de la internacionalización en .NET (cultura y fallback de recursos), el mecanismo de resx y los ensamblados satélite, el método realista según el framework, el cambio en tiempo de ejecución, y por último los aspectos que van más allá de las cadenas de texto (formato, diseño, RTL).

1. La conclusión primero (tabla de decisión)

El método de internacionalización se decide por la combinación de framework y requisitos.

Situación Método recomendado Motivo
WinForms, centrado en etiquetas/botones de pantalla Localizable = true del formulario + resx por idioma (método del diseñador) Permite ajustar por idioma el texto y también el tamaño y la posición de los controles, todo dentro de Visual Studio
WinForms/WPF comunes, muchos mensajes o textos compartidos resx compartido (Resources.resx + Resources.en.resx, etc.) + clase fuertemente tipada generada Las cadenas se referencian desde el código de forma segura en cuanto a tipos. La pantalla y la lógica pueden compartir el mismo texto
WPF resx compartido + enlace x:Static El método LocBaml no se puede usar en WPF de .NET (Core)1. El método resx permite compartir conocimiento con WinForms
El cambio de idioma en tiempo de ejecución es obligatorio resx + mecanismo de notificación de cambios (o reconstrucción de la pantalla) Antes de elegir el método, confirmar como requisito si de verdad hace falta un cambio sin reiniciar (capítulo 6)
La traducción se encarga a un tercero externo Diseñar sobre la base de resx, y entregar mediante una tabla de correspondencia entre claves y traducciones resx es XML, así que puede entregarse tal cual, pero la calidad depende de acompañarlo con información de contexto (de qué pantalla y qué botón se trata)

Adelantamos la conclusión.

  • CurrentCulture (formato) y CurrentUICulture (idioma de los recursos) son cosas distintas. Esta distinción es la base de todo el diseño de la internacionalización (capítulo 2).
  • El recurso en sí consiste en «recursos del idioma neutro + ensamblados satélite por idioma», y el fallback es automático. Se busca en el orden ja-JPja → neutro, de modo que, aunque falte una traducción, la aplicación no se cae y se muestra en el idioma por defecto2.
  • En WinForms, la solución práctica es combinar el método del diseñador (Localizable = true) con el método resx compartido. Las pantallas se llevan por el método del diseñador, y los cuadros de mensaje y los textos comunes se llevan al resx compartido (capítulo 4).
  • El método LocBaml de la guía oficial de WPF no se puede usar en .NET (Core)1. La práctica actual tiene como primera opción el método resx + x:Static (o enlace) (capítulo 5).
  • El cambio de idioma en tiempo de ejecución debería tener como primera opción «aplicarse en el próximo inicio». Los recursos de la pantalla que ya está mostrada no se sustituyen automáticamente (capítulo 6).
  • La traducción de las cadenas es solo la mitad de la internacionalización. La otra mitad es que el diseño acompañe la longitud de las cadenas traducidas, el formato de fechas y números, la fuente, y el soporte de idiomas que se escriben de derecha a izquierda (RTL) (capítulo 7).

2. La base — la diferencia entre CurrentCulture y CurrentUICulture

En la cultura (configuración regional) de .NET existen dos propiedades con funciones distintas3.

Propiedad Qué determina Ejemplo
CultureInfo.CurrentCulture El formato de fechas, números y moneda Mostrar 2026/07/07 o 7/7/2026
CultureInfo.CurrentUICulture En qué idioma se cargan los recursos Mostrar «Guardar» o «Save» en el botón

Estas dos propiedades se pueden configurar de forma independiente. Por ejemplo, la combinación «la pantalla en inglés, pero la fecha y la moneda en formato japonés» (un requisito habitual en aplicaciones de negocio para hablantes de otros idiomas dentro de Japón) se expresa de forma natural con CurrentUICulture = en y CurrentCulture = ja-JP a la vez.

Como ambos valores por defecto provienen de la configuración del sistema operativo, el punto de partida es «si no se hace nada, se sigue al sistema operativo». Cuando se quiere cambiar el idioma de forma explícita para toda la aplicación, en lugar de configurarlo hilo por hilo, se sustituyen los valores por defecto.

using System.Globalization;

// Sustituye la cultura por defecto de todos los hilos que se creen a partir de ahora
// (ejecutar antes de crear el hilo de la interfaz de usuario = antes de Application.Run)
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en"); // idioma de los recursos
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("ja-JP"); // formato

Asignar un valor a Thread.CurrentThread.CurrentUICulture solo tiene efecto en ese hilo. En aplicaciones de negocio que cruzan hilos con async/await o procesamiento en segundo plano, cambiar el valor por defecto en sí con DefaultThreadCurrentUICulture / DefaultThreadCurrentCulture evita descuidos3.

Por otro lado, el formato de los registros (logs), la salida a archivos y la integración entre sistemas no debe seguir la cultura del usuario: hay que fijarlo en CultureInfo.InvariantCulture. Para el diseño general del formato de fechas, consulte «Diseño de fecha, hora y zona horaria en aplicaciones de negocio».

3. El mecanismo de los recursos — resx, ensamblados satélite y fallback

La internacionalización de .NET sigue un modelo de «concentrador y radios» (hub and spoke). Los recursos del idioma por defecto (neutro) se incrustan en el ensamblado principal, y los recursos de cada idioma se separan en un DLL distinto llamado ensamblado satélite4.

Al compilar, los resx por idioma generan una estructura de salida como la siguiente.

MyApp.exe                     ← principal (incluye los recursos neutros)
MyApp.dll
ja/MyApp.resources.dll        ← ensamblado satélite en japonés
en/MyApp.resources.dll        ← ensamblado satélite en inglés

En tiempo de ejecución, el administrador de recursos parte de CurrentUICulture y busca los recursos en el orden cultura específica (ja-JP) → cultura neutra (ja) → valor por defecto (principal)2. Gracias a este fallback automático, aunque falte una sola traducción la aplicación no se cae y se muestra la cadena del idioma por defecto. Dicho de otro modo, que «se supone que la pantalla está en inglés pero aparece algo en japonés mezclado» es una señal de traducción faltante, y sirve como criterio de prueba (capítulo 8).

En la práctica conviene tener presentes estos dos puntos.

  • Declarar mediante el atributo NeutralResourcesLanguage cuál es la cultura del idioma por defecto elimina la búsqueda innecesaria del satélite cuando ya se está en la cultura por defecto, y deja claro el punto final del fallback4.
  • El ensamblado satélite solo funciona si se distribuye junto con el principal, por lo que hay que incluir las carpetas de idioma en el diseño del instalador o de la distribución por copia (capítulo 8).

Hay dos formas de declarar NeutralResourcesLanguage, según el tipo de proyecto. La primera es el formato antiguo con AssemblyInfo.cs manual (el valor por defecto en .NET Framework).

// Properties/AssemblyInfo.cs
using System.Resources;

// Declara que los recursos incrustados en el ensamblado principal están en japonés
[assembly: NeutralResourcesLanguage("ja-JP")]

En los proyectos de estilo SDK (el valor por defecto desde .NET 5 en adelante), AssemblyInfo.cs se genera automáticamente al compilar, por lo que se especifica mediante una propiedad en el archivo de proyecto.

<!-- MyApp.csproj -->
<PropertyGroup>
  <NeutralLanguage>ja-JP</NeutralLanguage>
</PropertyGroup>

Desde Visual Studio, puede configurarse lo mismo en las propiedades del proyecto, en [Paquete] > [General], en «Idioma neutro del ensamblado». Tenga en cuenta que si en un proyecto de estilo SDK se escriben ambas cosas, el atributo queda definido dos veces y se produce un error de compilación (CS0579). Use solo una de las dos opciones.

4. La práctica en WinForms — el método del diseñador y el método resx compartido

WinForms tiene dos métodos, y en la práctica se combinan.

4.1 Las pantallas, con el método del diseñador (Localizable = true)

Si en las propiedades del formulario se pone Localizable en true y se cambia la propiedad Language al idioma de destino (por ejemplo, inglés) antes de editar el Text de las etiquetas y los botones, Visual Studio genera automáticamente resx por idioma como Form1.en.resx5. Los pasos, en el orden en que se ejecutan, son los siguientes.

  1. Abrir el formulario en el diseñador y seleccionar el propio formulario, no un control (la zona de fondo)
  2. En la ventana de propiedades (F4), poner Localizable en True. En ese momento, las cadenas, el tamaño y la posición de los controles se vuelcan al Form1.resx del idioma por defecto
  3. En la misma ventana de propiedades, cambiar Language de «(Predeterminado)» al idioma de destino (por ejemplo, English)
  4. Con eso activo, editar el Text, el tamaño y la posición de las etiquetas y los botones. Los cambios se guardan en Form1.en.resx, y el resx del idioma por defecto no se modifica
  5. Al terminar, volver a poner Language en «(Predeterminado)»

Si se olvida el paso 5 y se continúa con cambios de diseño normales, esos cambios entran solo en el resx del idioma que se estaba editando, no en el del idioma por defecto, y el problema se descubre después en la forma de «solo en japonés la posición del botón está mal». En los equipos que usan el método del diseñador, conviene compartir desde el principio la advertencia de no olvidar devolver Language.

El punto fuerte de este método es que no solo las cadenas, sino también el tamaño y la posición de los controles se guardan por idioma. El problema de que «Registrar» en japonés se convierta en «Register» en inglés y se salga del botón puede ajustarse por idioma en el propio diseñador. Por otro lado, también trae un costo de mantenimiento: los resx aumentan en la cantidad de (número de idiomas × número de formularios), y cada cambio de diseño de un formulario exige revisar todos los idiomas. Si se prevé que el número de idiomas vaya a aumentar, priorice las medidas de diseño del capítulo 7 orientadas a lograr pantallas que «no se rompan sin necesidad de ajustarlas».

4.2 Los mensajes y los textos comunes, con el método resx compartido

El texto de los cuadros de mensaje, los diálogos de confirmación, la barra de estado y, en general, las cadenas que se usan desde el código, se concentran en un Resources.resx común al proyecto, y se añaden por idioma archivos como Resources.en.resx. Como a partir del resx se genera automáticamente una clase fuertemente tipada, desde el código se puede hacer referencia sin riesgo de errores al escribir la clave.

// Se referencia a través de la clase fuertemente tipada generada.
// La cadena devuelta cambia automáticamente según CurrentUICulture
MessageBox.Show(
    Properties.Resources.ConfirmDeleteMessage,
    Properties.Resources.ConfirmTitle,
    MessageBoxButtons.YesNo);

Aquí la regla más importante es no escribir directamente en el código las cadenas que se muestran al usuario. El esfuerzo de internacionalización no se gasta en introducir el mecanismo, sino en «recuperar las cadenas dispersas escritas directamente en el código». Incluso mientras se opera con un solo idioma, ya vale la pena canalizar las cadenas a través de resx por este motivo.

5. La práctica en WPF — resx como primera opción, no LocBaml

Antes que nada, organicemos los términos que aparecen en este capítulo.

Término Lectura o nombre completo Significado
BAML Binary Application Markup Language Formato binario resultante de compilar XAML en tiempo de compilación. En una aplicación WPF, lo que se incrusta en el ensamblado y se carga en tiempo de ejecución no es el XAML en sí, sino este BAML
LocBaml ── Una herramienta de ejemplo de la época de .NET Framework que extrae del BAML las cadenas a traducir, inserta las traducciones y reconstruye un BAML por idioma1
x:Static ── Notación en XAML para cargar directamente el valor de una propiedad, campo o constante estática del lado de C#. La carga ocurre una sola vez, en el momento en que se construye la pantalla, y si el valor cambia después, no se refleja en la pantalla
Ensamblado satélite ── El código_de_idioma/MyApp.resources.dll que contiene solo los recursos de cada idioma (capítulo 3)

La internacionalización de WPF es un área donde la información se mezcla con facilidad. La guía de la documentación oficial describe el método de configurar UICulture en el archivo de proyecto para generar recursos BAML e insertar las traducciones con la herramienta LocBaml, pero LocBaml es una herramienta de ejemplo exclusiva del WPF de .NET Framework y no funciona en el WPF de .NET (Core)1. Considere que ya no es un método a elegir para un desarrollo nuevo.

La primera opción en la práctica es aplicar desde XAML el mismo método resx compartido que en WinForms.

<!-- Después de poner el modificador de acceso del resx en Public, se referencia con x:Static -->
<Window xmlns:res="clr-namespace:MyApp.Properties"
        Title="{x:Static res:Resources.MainWindowTitle}">
    <Button Content="{x:Static res:Resources.SaveButton}" />
</Window>

La ventaja de este método es que el mecanismo de recursos (ensamblados satélite, fallback) es completamente común con WinForms, y en soluciones mixtas se puede gestionar el texto de forma centralizada. Como x:Static es una referencia estática que se resuelve al iniciar, si se necesita cambio en tiempo de ejecución hay que evolucionar hacia un enlace con notificación de cambios (capítulo 6).

En el lado de XAML, además, configurar el xml:lang del elemento raíz hace que funcionen correctamente las funciones de WPF que dependen del idioma, como la guionización, el fallback de fuentes y la visualización de números1.

Para el criterio de decidir si construir en WinForms o en WPF, consulte «Tabla de decisión para elegir entre WinForms, WPF y WinUI».

6. El cambio de idioma en tiempo de ejecución — «aplicarlo en el próximo inicio» como primera opción

Cuando el requisito es dejar elegir el idioma en una pantalla de configuración, hay que elegir el momento de aplicación entre dos opciones.

  • Opción A: aplicar en el próximo inicio (recomendada). Se guarda el idioma seleccionado en un archivo de configuración, y antes de Application.Run (o, en WPF, antes de que arranque Application) se establece DefaultThreadCurrentUICulture. La implementación es sencilla, y estructuralmente no aparecen los problemas asociados a reconstruir la pantalla (pérdida de datos que se estaban introduciendo, registro duplicado de controladores de eventos, etc.).
  • Opción B: aplicación inmediata. Aunque se cambie CurrentUICulture, las cadenas de la pantalla ya mostrada no se sustituyen automáticamente. En el método del diseñador de WinForms hay que volver a aplicar los recursos a los formularios abiertos con ComponentResourceManager.ApplyResources; en WPF hay que suministrar las cadenas mediante enlace con notificación de cambios. En cualquiera de los dos casos, la premisa es que «todas las pantallas están preparadas para volver a aplicar los recursos».

En las aplicaciones de negocio, la frecuencia con la que se cambia de idioma (normalmente, una sola vez, en la puesta en marcha) suele no compensar el costo de implementación y prueba de la opción B, por lo que se recomienda tomar la opción A como valor por defecto, y reservar la opción B solo para cuando el requisito realmente la exija. El lugar donde se guarda la configuración de idioma, al ser una configuración por usuario, debe estar bajo %LOCALAPPDATA%. Para el criterio sobre dónde ubicar la configuración, consulte la tabla de decisión de «La práctica de la gestión de configuración en aplicaciones de negocio Windows».

6.1 Guardar y leer la configuración de idioma (implementación de la opción A)

La opción A se resuelve con un código del siguiente orden. El destino de guardado es una carpeta propia bajo Environment.SpecialFolder.LocalApplicationData (= %LOCALAPPDATA%).

using System;
using System.Globalization;
using System.IO;

internal static class LanguageSetting
{
    private static readonly string Dir = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
        "MyCompany", "MyApp");

    private static readonly string FilePath = Path.Combine(Dir, "language.txt");

    // Guarda el idioma elegido en la pantalla de configuración (ejemplo: "en", "ja-JP")
    public static void Save(string cultureName)
    {
        Directory.CreateDirectory(Dir);
        File.WriteAllText(FilePath, cultureName);
    }

    // Llamar al iniciar, antes de Application.Run (o, en WPF, antes de que arranque Application).
    // Si no hay guardado o está dañado, no hace nada = sigue el idioma de visualización del sistema operativo
    public static void ApplyOnStartup()
    {
        if (!File.Exists(FilePath)) return;

        var name = File.ReadAllText(FilePath).Trim();
        if (name.Length == 0) return;

        try
        {
            CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo(name);
        }
        catch (CultureNotFoundException)
        {
            // Nombre de cultura desconocido. Se ignora la configuración y se mantiene el idioma del sistema operativo
        }
    }
}

El punto clave es que la ausencia de un archivo de configuración signifique «seguir al sistema operativo». Así, en el primer inicio o si el archivo se daña, no se detiene con una excepción, sino que cae de forma natural en el valor por defecto de CurrentUICulture (capítulo 2).

6.2 La forma mínima de implementar la opción B (aplicación inmediata)

Por si el requisito exige de forma imperativa la aplicación inmediata, se muestra el esqueleto de la implementación. En ambos casos sigue siendo cierto que la premisa es que «todas las pantallas estén incorporadas a este mecanismo».

Si en WinForms se usa el método del diseñador (Localizable = true), se vuelve a aplicar el resx por idioma con ComponentResourceManager a los formularios que están abiertos.

using System.ComponentModel;
using System.Globalization;
using System.Windows.Forms;

public partial class MainForm : Form
{
    // Se llama en cada formulario abierto después de cambiar de idioma
    public void ApplyCurrentLanguage(CultureInfo culture)
    {
        CultureInfo.DefaultThreadCurrentUICulture = culture;
        CultureInfo.CurrentUICulture = culture; // También surte efecto en el hilo de la interfaz de usuario en ejecución

        var resources = new ComponentResourceManager(GetType());

        // El propio formulario (título, tamaño) se guarda con la clave "$this"
        resources.ApplyResources(this, "$this");
        ApplyRecursive(Controls, resources);
    }

    private static void ApplyRecursive(
        Control.ControlCollection controls, ComponentResourceManager resources)
    {
        foreach (Control control in controls)
        {
            resources.ApplyResources(control, control.Name);
            ApplyRecursive(control.Controls, resources); // Recorre también los paneles anidados
        }
    }
}

Como esta recursión solo recorre Control, los elementos de menús y barras de herramientas (ToolStripItem no es un Control) requieren aplicar el mismo procesamiento por separado, recorriendo ToolStrip.Items. Que «solo el menú no cambia de idioma» tiene esta causa.

En WPF, se sustituye x:Static (una notación que solo lee el valor una vez) por un enlace, y al cambiar de idioma se emite una notificación de cambio.

using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;

// El origen que suministra las cadenas. Desde XAML se enlaza a esta instancia
public sealed class Loc : INotifyPropertyChanged
{
    public static Loc Instance { get; } = new Loc();

    private Loc() { }

    // Busca la clave del resx mediante el indexador. Si no se encuentra, devuelve el propio nombre de la clave
    public string this[string key] =>
        Properties.Resources.ResourceManager.GetString(key, CultureInfo.CurrentUICulture) ?? key;

    public event PropertyChangedEventHandler PropertyChanged;

    public void ChangeLanguage(CultureInfo culture)
    {
        CultureInfo.DefaultThreadCurrentUICulture = culture;
        CultureInfo.CurrentUICulture = culture;

        // Notificación de cambio para todo el indexador. El contenido de Binding.IndexerName es "Item[]"
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Binding.IndexerName));
    }
}
<!-- El modificador de acceso del resx debe ser Internal o Public -->
<Window xmlns:loc="clr-namespace:MyApp">
    <Button Content="{Binding Path=[SaveButton], Source={x:Static loc:Loc.Instance}}" />
</Window>

La única diferencia con la referencia directa x:Static del capítulo 5 es que, al tratarse de un enlace, se puede recibir la notificación de cambio. Como el mecanismo de resx y ensamblados satélite que hay detrás es el mismo, pasar más adelante de la opción A a la opción B no exige rehacer los recursos: se limita a sustituir la forma en que XAML los referencia.

7. Más allá de las cadenas — diseño, formato, fuentes y RTL

Aunque la traducción esté terminada, todavía queda la mitad de la internacionalización.

  • La flexibilidad del diseño. Aunque el significado sea el mismo, la longitud de un texto varía mucho según el idioma (japonés «保存» → alemán «Speichern»). Un diseño de coordenadas y tamaños fijos se rompe cada vez que se traduce, así que en WinForms el método recomendado oficialmente es combinar TableLayoutPanel y AutoSize para lograr un diseño que se adapte a la longitud de las cadenas6. En WPF, como el sistema de diseño ya es flexible por naturaleza, basta con evitar fijar anchos para cubrir la mayor parte de los casos. Conviene diseñar esto a la vez que las medidas contra la rotura en entornos de alto DPI («Guía de compatibilidad con DPI alto en WinForms», «Guía de compatibilidad con DPI alto en WPF»), para evitar retrabajo posterior.
  • El formato de fechas, números y moneda. Deje la visualización en pantalla a cargo de CurrentCulture, y reduzca en el código de pantalla los ToString("yyyy/MM/dd") fijados a mano. Por el contrario, fije en InvariantCulture los archivos, los registros (logs) y los datos de integración (capítulo 2).
  • Las fuentes. Compruebe si los caracteres del idioma de destino están incluidos en la fuente. Al añadir chino (simplificado o tradicional) o coreano, mantener la fuente pensada para japonés puede producir formas de carácter poco naturales (el llamado problema de las formas CJK). Concentrar en un solo lugar la especificación de la fuente de la interfaz facilita el cambio al añadir un idioma.
  • Idiomas que se escriben de derecha a izquierda (RTL). Para el soporte de árabe o hebreo, además de traducir las cadenas hace falta invertir la pantalla de izquierda a derecha. En WinForms se controla con las propiedades RightToLeft / RightToLeftLayout, y en WPF con la propiedad FlowDirection. Añadir el soporte de RTL «más adelante» obliga a revisar toda la pantalla, así que si existe la posibilidad de que entre un idioma de este tipo, inclúyalo en los requisitos desde el principio.
  • Texto dentro de imágenes e iconos. Las cadenas incrustadas en una imagen no cambian con el mecanismo de recursos. Es más seguro evitar imágenes con texto incrustado y usar la combinación de imagen + etiqueta de texto.

Para el criterio general de diseño de la interfaz, consulte también «Tabla de decisión de diseño de UX para aplicaciones Windows».

8. Operación — entrega de la traducción, detección de omisiones y distribución

Por último, los aspectos operativos una vez que el mecanismo ya está construido.

  • Acompañe la entrega de la traducción con contexto. Si solo se entrega la clave del resx y el texto original, la calidad de traducción de frases cortas como «OK» o «Abrir» no es estable. Es más realista intercambiar mediante una tabla de correspondencia (por ejemplo, una hoja de cálculo) que incluya la clave, el texto original, la traducción y «en qué pantalla se muestra y para qué se usa», y volcarla al resx una vez recibida.
  • Detecte las omisiones de traducción mediante el fallback. Gracias al fallback de recursos, una traducción faltante se manifiesta como «en la pantalla de ese idioma aparece mezclado el idioma por defecto»2. Incorporar en la integración continua (CI) una comprobación sencilla (un script o código de prueba) que contraste la lista de claves del resx de cada idioma con la del resx neutro permite detectarlo de forma mecánica antes del lanzamiento.
  • Incluya el ensamblado satélite en la distribución. Si no se distribuye junto con las carpetas de idioma como ja/ o en/, ese idioma cae en el fallback y se produce la situación de «se tradujo, pero no se refleja». Que falte una carpeta de idioma por una copia manual es un accidente habitual, así que hay que incluir toda la estructura de directorios en el instalador (MSI/MSIX, etc.). En la distribución con ClickOnce, por defecto se incluyen en el despliegue los ensamblados satélite de todos los idiomas7. Para el criterio general sobre el método de distribución, consulte «Cómo elegir el método de distribución de una aplicación Windows».

Resumen

La internacionalización de una aplicación de escritorio se apoya en un mecanismo probado, resx y ensamblados satélite, y el mecanismo en sí no es difícil. Lo que decide el éxito en la práctica es diseñar distinguiendo CurrentCulture de CurrentUICulture, la disciplina de no escribir directamente en el código las cadenas que se muestran al usuario, un diseño que resista la longitud de las cadenas traducidas, y evaluar como requisito si realmente hace falta el cambio inmediato. En WPF, en lugar de dejarse arrastrar por la información antigua sobre el método LocBaml, la solución práctica actual es construir sobre la base de resx.

Elegir el método de internacionalización de una aplicación existente y estimar el alcance del impacto, así como adaptar a varios idiomas las pantallas, los informes y los datos de integración de cara a la expansión hacia sedes en el extranjero, suele requerir decisiones que dependen del estado actual del código base, así que, si tiene dudas, no dude en consultarnos.

Artículos relacionados

Áreas de consultoría relacionadas

KomuraSoft LLC ofrece consultoría técnica sobre el diseño e implementación del soporte multilingüe en aplicaciones WinForms/WPF, y sobre la elección de método y la organización del alcance del impacto al internacionalizar más adelante una aplicación existente.

Referencias

  1. Microsoft Learn, WPF Globalization and Localization Overview. Sobre la generación de ensamblados satélite mediante la configuración de UICulture en el archivo de proyecto, el papel del xml:lang del elemento raíz, y el hecho de que LocBaml es una herramienta de ejemplo exclusiva del WPF de .NET Framework y no funciona en el WPF de .NET.  2 3 4 5

  2. Microsoft Learn, Retrieve resources in .NET apps. Sobre el proceso de fallback de recursos (que la búsqueda se realiza en el orden cultura específica → cultura neutra → recurso por defecto del ensamblado principal).  2 3

  3. Microsoft Learn, CultureInfo.CurrentUICulture Property. Sobre el uso de CurrentUICulture en la búsqueda de recursos específicos de cultura por parte del administrador de recursos, el hecho de que el valor por defecto provenga del idioma de la interfaz de usuario del sistema operativo, y la configuración del valor por defecto de todos los hilos mediante DefaultThreadCurrentUICulture 2

  4. Microsoft Learn, Resources in .NET apps. Sobre la creación y el empaquetado de recursos resx, el modelo de concentrador y radios formado por el ensamblado principal y los ensamblados satélite, y el atributo NeutralResourcesLanguage 2

  5. Microsoft Learn, Walkthrough: Localizing a Hybrid Application. Sobre el procedimiento de poner en true la propiedad Localizable en el diseñador de Windows Forms y cambiar la propiedad Language para generar resx por idioma (por ejemplo, Form1.es-ES.resx), y sobre el ejemplo de establecer CurrentUICulture antes de la llamada a InitializeComponent

  6. Microsoft Learn, How to: Design a Windows Forms Layout that Responds Well to Localization. Sobre cómo usar TableLayoutPanel para lograr un diseño en el que el tamaño de los controles acompañe el cambio de longitud de la propiedad Text provocado por la localización. 

  7. Microsoft Learn, Localize ClickOnce applications. Sobre el hecho de que incluir todos los ensamblados satélite en un solo despliegue es el valor por defecto de Visual Studio, y que en tiempo de ejecución se usa el ensamblado satélite correspondiente a la cultura del sistema operativo. 

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

Preguntas frecuentes

Preguntas habituales en las consultas sobre el tema del artículo.

¿Se puede internacionalizar más adelante una aplicación existente creada solo en japonés?
Sí, es posible, pero la mayor parte del trabajo no consiste en «introducir el mecanismo de recursos», sino en «localizar y reemplazar las cadenas escritas directamente en el código». Es necesario hacer un inventario que incluya no solo los textos de pantalla, sino también los cuadros de mensaje, los registros (logs), los mensajes de excepción, los informes y las cadenas de visualización almacenadas en la base de datos; el tamaño del trabajo depende más de la dispersión de las cadenas que del número de pantallas. Si desde la etapa de desarrollo inicial se canalizan a través de resx incluso las cadenas destinadas al usuario, el costo futuro de internacionalización se reduce drásticamente.
¿Es aceptable resolver la traducción con traducción automática?
Para herramientas internas o como solución provisional, es realista empezar con traducción automática. Sin embargo, las aplicaciones de negocio suelen tener muchas palabras cortas en botones y menús (por ejemplo, «Registrar», «Cancelar»), un terreno donde la tasa de errores de la traducción automática aumenta sin contexto. Como mínimo, se recomienda incluir una ronda de revisión en la que una persona que realmente use ese idioma pruebe las pantallas. Además, más importante que la calidad de la traducción es garantizar de antemano, con TableLayoutPanel y AutoSize, que el diseño no se rompa con cadenas traducidas más largas.
¿También deben traducirse los registros (logs) y los mensajes de error?
La práctica habitual es traducir los mensajes que se muestran en la pantalla del usuario y no traducir (fijar el idioma de) el contenido que se escribe en archivos de registro o en el registro de eventos. Los logs los leen desarrolladores y personal de operaciones, no el usuario final, y unos logs en varios idiomas arruinan la capacidad de búsqueda durante la investigación de incidentes. El formato (fechas, números) de los logs también debe fijarse en CultureInfo.InvariantCulture. Diseñar la aplicación separando CurrentCulture de CurrentUICulture permite lograr esta distinción de forma natural.
¿Conviene permitir cambiar el idioma solo dentro de la aplicación, independientemente del idioma de visualización del sistema operativo?
En la expansión hacia sedes en el extranjero, o en aplicaciones de negocio usadas dentro de Japón por personal que habla otros idiomas, tener una configuración de idioma propia dentro de la aplicación aporta mucho valor. Un esquema seguro de dos niveles es: por defecto seguir el idioma de visualización del sistema operativo (valor predeterminado de CurrentUICulture), y sobrescribirlo solo cuando el usuario lo elige explícitamente en la pantalla de configuración. Aplicar el cambio «en el próximo inicio» es la opción más segura, y también simplifica tanto la implementación como la explicación operativa.

Perfil del autor

Página de presentación del autor del artículo.

Go Komura

Representante de KomuraSoft LLC

Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.

Volver al blog