私はまもなく大規模なc#プロジェクトに取り組み、最初から多言語サポートを組み込みたいと考えています。私は遊んだことがあり、言語ごとに個別のリソースファイルを使用してそれを機能させることができ、次にリソースマネージャーを使用して文字列をロードします。
私が調べることができる他の良いアプローチはありますか?
私はまもなく大規模なc#プロジェクトに取り組み、最初から多言語サポートを組み込みたいと考えています。私は遊んだことがあり、言語ごとに個別のリソースファイルを使用してそれを機能させることができ、次にリソースマネージャーを使用して文字列をロードします。
私が調べることができる他の良いアプローチはありますか?
回答:
私はと現在のソリューション持つ、アウト経験からこれを言うことができる12件の 24 API、MVC、プロジェクトライブラリ(コア機能)、WPF、UWPとXamarinが含まれるプロジェクトを。この長い投稿を読むのが最善の方法だと思うので、読む価値があります。VSツールを使用すると、簡単にエクスポートおよびインポートして、翻訳会社に送信したり、他の人がレビューしたりできます。
編集02/2018:引き続き強力であり、.NET Standardライブラリに変換すると、.NETFrameworkとNETCore全体で使用することも可能になります。たとえばangularが使用できるように、JSONに変換するためのセクションを追加しました。
編集2019: Xamarinを使用して、これはすべてのプラットフォームで引き続き機能します。たとえば、resxファイルも使用するためのXamarin.Formsのアドバイス。(私はまだXamarin.Formsでアプリを開発していませんが、ドキュメント、つまり開始するための詳細な方法で、Xamarin.Formsドキュメントで説明しています)。JSONに変換するのと同じように、Xamarin.Androidの.xmlファイルに変換することもできます。
編集2019(2): WPFからUWPにアップグレードしているときに、UWPで別のファイルタイプを使用することを好むことに気付きました.resw
。これは、コンテンツは同じですが、使用法が異なります。私の意見では、これを行う別の方法を見つけました。これは、デフォルトのソリューションよりもうまく機能します。
2020年の編集:複数の言語のプロジェクトを必要とする可能性のある大規模な(モジュラー)プロジェクトのいくつかの提案を更新しました。
だから、それに取り掛かろう。
プロの
ResourceDirectories
。Thread.CurrentThread.CurrentCulture
コンズ
セットアップ
ソリューションで言語プロジェクトを作成し、MyProject.Languageのような名前を付けます。Resourcesというフォルダーをそのフォルダーに追加し、そのフォルダーに2つのResourcesファイル(.resx)を作成します。1つはResources.resxと呼ばれ、もう1つはResources.en.resx(または特定の場合は.en-GB.resx)と呼ばれます。私の実装では、デフォルト言語としてNL(オランダ語)言語を使用しているため、これが最初のファイルに含まれ、英語が2番目のファイルに含まれます。
セットアップは次のようになります。
Resources.resxのプロパティは次のとおりである必要があります。
カスタムツールの名前空間がプロジェクトの名前空間に設定されていることを確認してください。この理由は、WPFではResources
XAMLの内部を参照できないためです。
そして、リソースファイル内で、アクセス修飾子をPublicに設定します。
このような大規模なアプリケーション(たとえば、異なるモジュール)がある場合は、上記のように複数のプロジェクトを作成することを検討できます。その場合、キーとリソースクラスの前に特定のモジュールを付けることができます。Visual Studioに最適な言語エディターを使用して、すべてのファイルを1つの概要に結合します。
別のプロジェクトで使用する
プロジェクトへの参照:[参照]-> [参照の追加]-> [Prjects \ Solutions]を右クリックします。
ファイルで名前空間を使用します。 using MyProject.Language;
バックエンドでそのように使用し
string someText = Resources.orderGeneralError;
ます。Resourcesと呼ばれるものが他にある場合は、名前空間全体に配置します。
MVCでは、言語を設定することはできますが、パラメーター化されたURLを使用しました。これは、次のように設定できます。
RouteConfig.cs 他のマッピングの下
routes.MapRoute(
name: "Locolized",
url: "{lang}/{controller}/{action}/{id}",
constraints: new { lang = @"(\w{2})|(\w{2}-\w{2})" }, // en or en-US
defaults: new { controller = "shop", action = "index", id = UrlParameter.Optional }
);
FilterConfig.cs(追加する必要がある場合があります。追加する場合FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
は、のApplication_start()
メソッドに追加します。Global.asax
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ErrorHandler.AiHandleErrorAttribute());
//filters.Add(new HandleErrorAttribute());
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は、カルチャ情報を設定するだけです。
//fixed number and date format for now, this can be improved.
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;
//later, we will if-else the language here
info.DateTimeFormat.DateSeparator = "/";
info.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";
Thread.CurrentThread.CurrentUICulture = info;
Thread.CurrentThread.CurrentCulture = info;
}
catch (Exception)
{
}
}
.cshtmlでの使用法
@using MyProject.Language;
<h3>@Resources.w_home_header</h3>
または、用途を定義したくない場合は、名前空間全体に入力するか、/ 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>
このMVC実装ソースチュートリアル:素晴らしいチュートリアルブログ
モデルのクラスライブラリでの使用
バックエンドでの使用は同じですが、属性で使用するための単なる例です
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; }
}
}
リシェーパーがある場合は、指定されたリソース名が存在するかどうかが自動的にチェックされます。型安全性を好む場合は、T4テンプレートを使用して列挙型を生成できます
もちろん、MyProject.Language名前空間への参照を追加します。これは、バックエンドでの使用方法を知っています。
XAMLで、WindowまたはUserControlのヘッダー内に、次のlang
ように呼ばれる名前空間参照を追加します。
<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">
次に、ラベル内:
<Label x:Name="lblHeader" Content="{x:Static lang:Resources.w_home_header}" TextBlock.FontSize="20" HorizontalAlignment="Center"/>
強く型付けされているので、リソース文字列が存在することを確認できます。セットアップ中にプロジェクトを再コンパイルする必要がある場合があります。WPFは新しい名前空間でバグがある場合があります。
WPFのもう1つのことは、内に言語を設定することApp.xaml.cs
です。独自の実装を行うか(インストール時に選択)、システムに決定させることができます。
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://default english because there can be so many different system language, we rather fallback on english in this case.
MyProject.Language.Resources.Culture = new System.Globalization.CultureInfo("en-GB");
break;
}
}
}
UWPでは、Microsoftはこのソリューションを使用します。つまり、新しいリソースファイルを作成する必要があります。さらにx:Uid
、XAMLのコントロールのをリソースのキーに設定するように求められているため、テキストを再利用することもできません。そして、あなたのリソースではExample.Text
、TextBlock
のテキストを埋めるためにあなたがしなければなりません。リソースファイルを再利用したいので、そのソリューションはまったく好きではありませんでした。最終的に私は次の解決策を思いついた。今日(2019-09-26)にこれを見つけたので、これが期待どおりに機能しないことが判明した場合は、別の何かを返す可能性があります。
これをプロジェクトに追加します。
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);
}
}
これをApp.xaml.cs
コンストラクターに追加します。
CustomXamlResourceLoader.Current = new MyXamlResourceLoader();
アプリ内のどこでも、これを使用して言語を変更します。
ApplicationLanguages.PrimaryLanguageOverride = "nl";
Frame.Navigate(this.GetType());
UIを更新するには、最後の行が必要です。私はまだこのプロジェクトに取り組んでいる間に、これを2回行う必要があることに気づきました。ユーザーが最初に起動するときに、言語を選択してしまう可能性があります。ただし、これはWindowsストア経由で配布されるため、通常、言語はシステム言語と同じです。
次に、XAMLで使用します。
<TextBlock Text="{CustomResource ExampleResourceKey}"></TextBlock>
今日では、Angularのようなフレームワークをコンポーネントと組み合わせて使用することが一般的になっているため、cshtmlは使用していません。翻訳はjsonファイルに保存されます。これがどのように機能するかについては説明しません。角度のある複数翻訳ではなく、ngx-translateを強くお勧めします。したがって、翻訳をJSONファイルに変換する場合は、非常に簡単です。リソースファイルをjsonファイルに変換するT4テンプレートスクリプトを使用します。いくつかの変更を行う必要があるため、構文を読み取って正しく使用するには、T4エディターをインストールすることをお勧めします。
注意すべきことは1つだけです。データを生成、コピー、クリーンアップして、別の言語用に生成することはできません。したがって、以下のコードを使用している言語の数だけコピーし、「//ここで言語を選択」の前にエントリを変更する必要があります。現在、これを修正する時間はありませんが、おそらく後で更新されます(興味がある場合)。
パス: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";//choose language here
var fileResultPath = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", fileResultName);
//var fileDestinationPath = "../../MyProject.Web/ClientApp/app/i18n/";
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 }; //choose language here
string[] fileNamesDest = new string[] {fileNameDestEn }; //choose language here
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);
}
#>
modulairアプリケーションがあり、複数の言語プロジェクトを作成するという私の提案に従った場合は、それぞれにT4ファイルを作成する必要があります。jsonファイルが論理的に定義されていることを確認してください。論理的に定義する必要はありません。en.json
また、定義することもできますexample-en.json
。ngx-translateで使用するために複数のjsonファイルを組み合わせるには、こちらの手順に従ってください
上記のアップデートで説明したように、私はAngular / JSONで行ったのと同じ方法を使用します。しかし、AndroidはXMLファイルを使用するので、それらのXMLファイルを生成するT4ファイルを作成しました。
パス: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;
//if(!name.ToString().Contains("WEBSHOP_") && !name.ToString().Contains("DASHBOARD_"))//only include keys with these prefixes, or the country ones.
//{
// if(name.ToString().Length != 2)
// {
// continue;
// }
//}
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("<<", "");
//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);
}
#>
Androidはvalues-xx
フォルダーで動作するため、上記はフォルダー内の英語用values-en
です。ただし、values
フォルダに入るデフォルトも生成する必要があります。上記のT4テンプレートをコピーして、上記のコードのフォルダーを変更するだけです。
これで、すべてのプロジェクトに1つのリソースファイルを使用できるようになりました。これにより、すべてをexclドキュメントにエクスポートし、誰かがそれを翻訳して再度インポートできるようにすることが非常に簡単になります。
ファイルで素晴らしい働きをするこの素晴らしいVS拡張機能に特に感謝しresx
ます。彼の素晴らしい仕事のために彼に寄付することを検討してください(私はそれとは何の関係もありません、私はただ拡張機能が大好きです)。
さまざまなアプローチを使用して実装されたプロジェクトを見てきましたが、それぞれに長所と短所があります。
選択したリソース方法は非常に理にかなっていると思います。このようなことをするより良い方法があるかどうか私はしばしば疑問に思うので、他の人の答えを見るのも面白いでしょう。私は、ここSOにあるものを含め、すべてがリソースの使用方法を指している多数のリソースを見てきました。
「最善の方法」はないと思います。それは本当にあなたが構築しているテクノロジーとアプリケーションのタイプに依存します。
Webappsは、他の投稿者が示唆しているようにデータベースに情報を保存できますが、別のリソースファイルを使用することをお勧めします。これは、ソースとは別のリソースファイルです。リソースファイルを分離すると、同じファイルの競合が減り、プロジェクトが大きくなるにつれて、ローカリゼーションがビジネスロジックとは別に行われることがあります。(プログラマーと翻訳者)。
Microsoft WinFormおよびWPFの専門家は、ロケールごとにカスタマイズされた個別のリソースアセンブリを使用することをお勧めします。
UI要素をコンテンツに合わせてサイズ変更するWPFの機能により、必要なレイアウト作業が軽減されます。例:(日本語の単語は英語よりもはるかに短い)。
WPFを検討している場合:このmsdnの記事 を読むことをお勧めします。正直に言うと、WPFローカリゼーションツールであるmsbuild、locbaml(およびExcelスプレッドシート)を使用するのは面倒ですが、機能します。
わずかに関連するもの:私が直面する一般的な問題は、エラーコードではなく、エラーメッセージ(通常は英語)を送信するレガシーシステムを統合することです。これにより、レガシーシステムへの変更が強制されるか、バックエンド文字列が自分のエラーコードにマッピングされてから、ローカライズされた文字列にマッピングされます...そうです。 エラーコードはローカリゼーションの友です
+1データベース
データベースに修正が加えられた場合、アプリ内のフォームはその場で再翻訳することもできます。
すべてのコントロールがXMLファイル(フォームごとに1つ)で言語リソースIDにマップされているシステムを使用しましたが、すべてのIDはデータベースにありました。
基本的に、各コントロールにIDを保持させる(インターフェイスを実装する、またはVB6のtagプロパティを使用する)代わりに、.NETではコントロールツリーがリフレクションによって簡単に検出できるという事実を使用しました。フォームがロードされたときのプロセスは、XMLファイルが欠落している場合にそれを構築します。XMLファイルはコントロールをリソースIDにマップするため、これを入力してデータベースにマップするだけで済みます。これは、何かがタグ付けされていない場合、または別のIDに分割する必要がある場合(名詞と動詞の両方として使用される可能性のある英語の一部の単語を2つの異なる単語に翻訳する必要がある場合)、コンパイルされたバイナリを変更する必要がないことを意味しました辞書にあり、再利用されませんが、IDの最初の割り当て中にこれを発見できない場合があります)。
アプリがより関与するのは、挿入ポイントのあるフェーズが使用される場合だけです。
データベース翻訳ソフトウェアは、不足している翻訳などを簡単に処理するためのさまざまなワークフローオプションを備えた基本的なCRUDメンテナンス画面でした。
私は検索していて、これを見つけました:
あなたは、WPFやSilverlightを使用している場合、あなたのaproachは、使用可能性がありWPF LocalizationExtension多くの理由のために。
ITのオープンソースそれは無料です(そして無料のままです)は本当のstabel状態です
Windowsアプリケーションでは、次のような操作を行うことができます。
public partial class App : Application
{
public App()
{
}
protected override void OnStartup(StartupEventArgs e)
{
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-DE"); ;
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("de-DE"); ;
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(
XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));
base.OnStartup(e);
}
}
そして、Wep Pageでは、アプローチは同じである可能性があると思います。
幸運を!
複数のリソースファイルを使用します。設定するのはそれほど難しいことではありません。実際、私は最近、フォーム言語のリソースファイルと組み合わせてグローバル言語ベースのリソースファイルを設定することに関する同様の質問に答えました。
少なくともWinForm開発には最善のアプローチだと思います。
Sisulizerのような市販のツールを使用できます。言語ごとに衛星アセンブリを作成します。注意が必要なのは、フォームクラス名を難読化しないことだけです(難読化ツールを使用している場合)。
標準のリソースファイルの方が簡単です。ただし、ルックアップテーブルなどの言語依存データがある場合は、2つのリソースセットを管理する必要があります。
私はそれをしていませんが、次のプロジェクトではデータベースリソースプロバイダーを実装します。私はMSDNでそれを行う方法を見つけました:
http://msdn.microsoft.com/en-us/library/aa905797.aspx
私もこの実装を見つけました: