Use um projeto separado com recursos
Posso dizer isso por experiência própria, tendo uma solução atual com 12 24 projetos que inclui API, MVC, Bibliotecas de Projetos (funcionalidades do Core), WPF, UWP e Xamarin. Vale a pena ler este longo post, pois acho que é a melhor maneira de fazer isso. Com a ajuda de ferramentas VS facilmente exportáveis e importáveis para enviar para agências de tradução ou revisão por outras pessoas.
EDITAR 02/2018: Ainda forte, convertê-lo em uma biblioteca .NET Standard torna possível até mesmo usá-lo em .NET Framework e NET Core. Eu adicionei uma seção extra para convertê-lo em JSON para que, por exemplo, o angular possa usá-lo.
EDITAR 2019: Avançando com o Xamarin, ainda funciona em todas as plataformas. Por exemplo, conselhos do Xamarin.Forms para usar arquivos resx também. (Eu não desenvolvi um aplicativo em Xamarin.Forms ainda, mas a documentação, que é detalhada para começar, cobre: Documentação do Xamarin.Forms ). Assim como convertê-lo para JSON, também podemos convertê-lo em um arquivo .xml para Xamarin.Android.
EDIT 2019 (2): Ao atualizar para UWP a partir do WPF, descobri que na UWP eles preferem usar outro tipo de arquivo .resw
, que é idêntico em termos de conteúdo, mas o uso é diferente. Eu encontrei uma maneira diferente de fazer isso que, na minha opinião, funciona melhor do que a solução padrão .
EDIT 2020: Atualizado algumas sugestões para projetos maiores (modulair) que podem exigir vários projetos de idioma.
Então vamos fazer isso.
Pro's
- Fortemente digitado em quase todos os lugares.
- No WPF você não tem que lidar
ResourceDirectories
.
- Compatível com ASP.NET, Bibliotecas de classes, WPF, Xamarin, .NET Core, .NET Standard até onde testei.
- Nenhuma biblioteca extra de terceiros necessária.
- Suporta fallback de cultura: en-US -> en.
- Não apenas back-end, funciona também em XAML para WPF e Xamarin.Forms, em .cshtml para MVC.
- Manipule facilmente o idioma alterando o
Thread.CurrentThread.CurrentCulture
- Os mecanismos de pesquisa podem rastrear em diferentes idiomas e o usuário pode enviar ou salvar urls específicas de um idioma.
Con's
- Às vezes, o WPF XAML apresenta erros, as cadeias de caracteres recém-adicionadas não aparecem diretamente. Reconstruir é a correção temporária (vs2015).
- O UWP XAML não mostra sugestões de intellisense e não mostra o texto durante o projeto.
- Conte-me.
Configuração
Crie um projeto de linguagem em sua solução, dê a ele um nome como MyProject.Language . Adicione uma pasta chamada Recursos e, nessa pasta, crie dois arquivos de Recursos (.resx). Um denominado Resources.resx e outro denominado Resources.en.resx (ou .en-GB.resx para específico). Na minha implementação, tenho o idioma NL (holandês) como o idioma padrão, então isso vai no meu primeiro arquivo e o inglês vai no meu segundo arquivo.
A configuração deve ser semelhante a esta:
As propriedades de Resources.resx devem ser:
Certifique-se de que o namespace da ferramenta personalizada esteja definido como o namespace do seu projeto. A razão para isso é que no WPF, você não pode fazer referência a Resources
dentro do XAML.
E dentro do arquivo de recurso, defina o modificador de acesso como Público:
Se você tiver um aplicativo tão grande (digamos, módulos diferentes), você pode considerar a criação de vários projetos como acima. Nesse caso, você pode prefixar suas Chaves e classes de recursos com o Módulo específico. Use o melhor editor de linguagem existente para o Visual Studio para combinar todos os arquivos em uma única visão geral.
Usando em outro projeto
Referência ao seu projeto: Clique com o botão direito em References -> Add Reference -> Prjects \ Solutions.
Use namespace em um arquivo: using MyProject.Language;
Use-o assim no back-end:
string someText = Resources.orderGeneralError;
se houver outra coisa chamada Recursos, basta inserir o namespace inteiro.
Usando em MVC
Em MVC você pode fazer o que quiser para definir o idioma, mas eu usei urls parametrizadas, que podem ser configuradas da seguinte forma:
RouteConfig.cs
Abaixo dos outros mapeamentos
routes.MapRoute(
name: "Locolized",
url: "{lang}/{controller}/{action}/{id}",
constraints: new { lang = @"(\w{2})|(\w{2}-\w{2})" },
defaults: new { controller = "shop", action = "index", id = UrlParameter.Optional }
);
FilterConfig.cs (pode precisar ser adicionado, se assim for, adicione FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
ao Application_start()
método emGlobal.asax
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ErrorHandler.AiHandleErrorAttribute());
filters.Add(new LocalizationAttribute("nl-NL"), 0);
}
}
LocalizationAttribute
public class LocalizationAttribute : ActionFilterAttribute
{
private string _DefaultLanguage = "nl-NL";
private string[] allowedLanguages = { "nl", "en" };
public LocalizationAttribute(string defaultLanguage)
{
_DefaultLanguage = defaultLanguage;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string lang = (string) filterContext.RouteData.Values["lang"] ?? _DefaultLanguage;
LanguageHelper.SetLanguage(lang);
}
}
LanguageHelper apenas define as informações de cultura.
public static void SetLanguage(LanguageEnum language)
{
string lang = "";
switch (language)
{
case LanguageEnum.NL:
lang = "nl-NL";
break;
case LanguageEnum.EN:
lang = "en-GB";
break;
case LanguageEnum.DE:
lang = "de-DE";
break;
}
try
{
NumberFormatInfo numberInfo = CultureInfo.CreateSpecificCulture("nl-NL").NumberFormat;
CultureInfo info = new CultureInfo(lang);
info.NumberFormat = numberInfo;
info.DateTimeFormat.DateSeparator = "/";
info.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";
Thread.CurrentThread.CurrentUICulture = info;
Thread.CurrentThread.CurrentCulture = info;
}
catch (Exception)
{
}
}
Uso em .cshtml
@using MyProject.Language;
<h3>@Resources.w_home_header</h3>
ou se você não quiser definir usings, basta preencher todo o namespace OU você pode definir o namespace em /Views/web.config:
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
...
<add namespace="MyProject.Language" />
</namespaces>
</pages>
</system.web.webPages.razor>
Este tutorial da fonte de implementação do mvc: Blog de tutorial incrível
Usando bibliotecas de classe para modelos
O uso de back-end é o mesmo, mas apenas um exemplo de uso em atributos
using MyProject.Language;
namespace MyProject.Core.Models
{
public class RegisterViewModel
{
[Required(ErrorMessageResourceName = "accountEmailRequired", ErrorMessageResourceType = typeof(Resources))]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
}
}
Se você tiver remodelado, ele verificará automaticamente se o nome do recurso fornecido existe. Se você preferir segurança de tipo, você pode usar modelos T4 para gerar um enum
Usando em WPF.
Claro, adicione uma referência ao seu namespace MyProject.Language , sabemos como usá-lo no back-end.
Em XAML, dentro do cabeçalho de uma janela ou UserControl, adicione uma referência de namespace chamada lang
assim:
<UserControl x:Class="Babywatcher.App.Windows.Views.LoginView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyProject.App.Windows.Views"
xmlns:lang="clr-namespace:MyProject.Language;assembly=MyProject.Language" <!--this one-->
mc:Ignorable="d"
d:DesignHeight="210" d:DesignWidth="300">
Então, dentro de um rótulo:
<Label x:Name="lblHeader" Content="{x:Static lang:Resources.w_home_header}" TextBlock.FontSize="20" HorizontalAlignment="Center"/>
Uma vez que é fortemente tipado, você tem certeza de que a string de recurso existe. Pode ser necessário recompilar o projeto às vezes durante a configuração, às vezes o WPF apresenta erros com novos namespaces.
Mais uma coisa para o WPF, defina o idioma dentro do App.xaml.cs
. Você pode fazer sua própria implementação (escolha durante a instalação) ou deixar o sistema decidir.
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
SetLanguageDictionary();
}
private void SetLanguageDictionary()
{
switch (Thread.CurrentThread.CurrentCulture.ToString())
{
case "nl-NL":
MyProject.Language.Resources.Culture = new System.Globalization.CultureInfo("nl-NL");
break;
case "en-GB":
MyProject.Language.Resources.Culture = new System.Globalization.CultureInfo("en-GB");
break;
default:
MyProject.Language.Resources.Culture = new System.Globalization.CultureInfo("en-GB");
break;
}
}
}
Usando em UWP
Na UWP, a Microsoft usa essa solução , o que significa que você precisará criar novos arquivos de recursos. Além disso, você não pode reutilizar o texto porque eles querem que você defina o x:Uid
de seu controle em XAML como uma chave em seus recursos. E em seus recursos você tem que fazer Example.Text
para preencher um TextBlock
texto de. Eu não gostei dessa solução porque quero reutilizar meus arquivos de recursos. Eventualmente, encontrei a seguinte solução. Acabei de descobrir isso hoje (26/09/2019), então posso voltar com outra coisa, se isso não funcionar como desejado.
Adicione isto ao seu projeto:
using Windows.UI.Xaml.Resources;
public class MyXamlResourceLoader : CustomXamlResourceLoader
{
protected override object GetResource(string resourceId, string objectType, string propertyName, string propertyType)
{
return MyProject.Language.Resources.ResourceManager.GetString(resourceId);
}
}
Adicione isto a App.xaml.cs
no construtor:
CustomXamlResourceLoader.Current = new MyXamlResourceLoader();
Sempre que você quiser em seu aplicativo, use isto para alterar o idioma:
ApplicationLanguages.PrimaryLanguageOverride = "nl";
Frame.Navigate(this.GetType());
A última linha é necessária para atualizar a IU. Enquanto ainda estou trabalhando neste projeto, percebi que precisava fazer isso 2 vezes. Posso acabar com uma seleção de idioma na primeira vez que o usuário iniciar. Mas, como será distribuído por meio da Windows Store, o idioma geralmente é igual ao idioma do sistema.
Em seguida, use em XAML:
<TextBlock Text="{CustomResource ExampleResourceKey}"></TextBlock>
Usando no Angular (converter para JSON)
Hoje em dia é mais comum ter um framework como o Angular em combinação com componentes, portanto, sem cshtml. As traduções são armazenadas em arquivos json, não vou cobrir como isso funciona, eu recomendo fortemente o ngx-translate em vez da multi-tradução angular. Portanto, se você deseja converter traduções em um arquivo JSON, é muito fácil, eu uso um script de modelo T4 que converte o arquivo de Recursos em um arquivo json. Eu recomendo instalar o editor T4 para ler a sintaxe e usá-lo corretamente porque você precisa fazer algumas modificações.
Apenas uma coisa a se notar: não é possível gerar os dados, copiá-los, limpar os dados e gerá-los para outro idioma. Portanto, você deve copiar o código abaixo tantas vezes quanto os idiomas que possui e alterar a entrada antes de '// escolher o idioma aqui'. Atualmente não há tempo para consertar isso, mas provavelmente atualizarei mais tarde (se estiver interessado).
Caminho: MyProject.Language / T4 / CreateLocalizationEN.tt
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Windows.Forms" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Resources" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.ComponentModel.Design" #>
<#@ output extension=".json" #>
<#
var fileNameNl = "../Resources/Resources.resx";
var fileNameEn = "../Resources/Resources.en.resx";
var fileNameDe = "../Resources/Resources.de.resx";
var fileNameTr = "../Resources/Resources.tr.resx";
var fileResultName = "../T4/CreateLocalizationEN.json";
var fileResultPath = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", fileResultName);
var fileNameDestNl = "nl.json";
var fileNameDestEn = "en.json";
var fileNameDestDe = "de.json";
var fileNameDestTr = "tr.json";
var pathBaseDestination = Directory.GetParent(Directory.GetParent(this.Host.ResolvePath("")).ToString()).ToString();
string[] fileNamesResx = new string[] {fileNameEn };
string[] fileNamesDest = new string[] {fileNameDestEn };
for(int x = 0; x < fileNamesResx.Length; x++)
{
var currentFileNameResx = fileNamesResx[x];
var currentFileNameDest = fileNamesDest[x];
var currentPathResx = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", currentFileNameResx);
var currentPathDest =pathBaseDestination + "/MyProject.Web/ClientApp/app/i18n/" + currentFileNameDest;
using(var reader = new ResXResourceReader(currentPathResx))
{
reader.UseResXDataNodes = true;
#>
{
<#
foreach(DictionaryEntry entry in reader)
{
var name = entry.Key;
var node = (ResXDataNode)entry.Value;
var value = node.GetValue((ITypeResolutionService) null);
if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("\n", "");
if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("\r", "");
#>
"<#=name#>": "<#=value#>",
<#
}
#>
"WEBSHOP_LASTELEMENT": "just ignore this, for testing purpose"
}
<#
}
File.Copy(fileResultPath, currentPathDest, true);
}
#>
Se você tem um aplicativo modulair e seguiu minha sugestão de criar projetos em vários idiomas, terá que criar um arquivo T4 para cada um deles. Certifique-se de que os arquivos json estão definidos logicamente, não precisa ser en.json
, também pode ser example-en.json
. Para combinar vários arquivos json para usar com ngx-translate , siga as instruções aqui
Use no Xamarin.Android
Conforme explicado acima nas atualizações, eu uso o mesmo método que usei com Angular / JSON. Mas o Android usa arquivos XML, então escrevi um arquivo T4 que gera esses arquivos XML.
Caminho: MyProject.Language / T4 / CreateAppLocalizationEN.tt
#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Windows.Forms" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Resources" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.ComponentModel.Design" #>
<#@ output extension=".xml" #>
<#
var fileName = "../Resources/Resources.en.resx";
var fileResultName = "../T4/CreateAppLocalizationEN.xml";
var fileResultRexPath = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", fileName);
var fileResultPath = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", fileResultName);
var fileNameDest = "strings.xml";
var pathBaseDestination = Directory.GetParent(Directory.GetParent(this.Host.ResolvePath("")).ToString()).ToString();
var currentPathDest =pathBaseDestination + "/MyProject.App.AndroidApp/Resources/values-en/" + fileNameDest;
using(var reader = new ResXResourceReader(fileResultRexPath))
{
reader.UseResXDataNodes = true;
#>
<resources>
<#
foreach(DictionaryEntry entry in reader)
{
var name = entry.Key;
var node = (ResXDataNode)entry.Value;
var value = node.GetValue((ITypeResolutionService) null);
if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("\n", "");
if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("\r", "");
if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("&", "&");
if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("<<", "");
#>
<string name="<#=name#>">"<#=value#>"</string>
<#
}
#>
<string name="WEBSHOP_LASTELEMENT">just ignore this</string>
<#
#>
</resources>
<#
File.Copy(fileResultPath, currentPathDest, true);
}
#>
O Android funciona com values-xx
pastas, portanto, acima é para Inglês para na values-en
pasta. Mas você também deve gerar um padrão que vai para a values
pasta. Basta copiar o modelo T4 acima e alterar a pasta no código acima.
Pronto, agora você pode usar um único arquivo de recurso para todos os seus projetos. Isso torna muito fácil exportar tudo para um documento excl e permitir que alguém o traduza e importe novamente.
Agradecimentos especiais a esta extensão VS incrível que funciona muito bem com resx
arquivos. Considere fazer uma doação a ele por seu trabalho incrível (não tenho nada a ver com isso, simplesmente amo a extensão).