私はまもなく大規模なc#プロジェクトに取り組み、最初から多言語サポートを組み込みたいと考えています。いろいろ試してみましたが、言語ごとに個別のリソースファイルを使用して動作させ、リソースマネージャーを使用して文字列を読み込むことができます。
私が検討できる他の良いアプローチはありますか?
私は経験からこれを伝えることができ、現在のソリューションを持っています 12 24API、MVC、プロジェクトライブラリ(コア機能)、WPF、およびXamarinを含むプロジェクト。この長い投稿を読むのは価値があると思います。 VSツールの助けを借りて、簡単にエクスポートおよびインポートして、翻訳会社に送信したり、他の人がレビューしたりできます。
EDIT 02/2018:まだ強力ですが、.NET標準ライブラリに変換すると、.NET FrameworkとNET Coreで使用することも可能になります。たとえば、angularで使用できるように、JSONに変換するための追加セクションを追加しました。
EDIT:2019:Xamarinを使用して前進しているので、これはすべてのプラットフォームで機能します。例えば。 Xamarin.Formsは、resxファイルも使用するようにアドバイスしています。 (Xamarin.Formsでアプリをまだ開発していませんが、開始するための詳細な方法であるドキュメントで説明しています: Xamarin.Forms Documentation )。 JSONに変換するように、Xamarin.Android(現在作業中)の.xmlファイルに変換することもできます。
だから、それに到達することができます。
Pro's
ResourceDirectories
を扱う必要はありません。Thread.CurrentThread.CurrentCulture
_を変更して言語を簡単に操作しますCon's
セットアップ
ソリューションで言語プロジェクトを作成し、MyProject.Languageのような名前を付けます。 Resourcesという名前のフォルダーを追加し、そのフォルダーに2つのResourcesファイル(.resx)を作成します。 1つはResources.resxと呼ばれ、もう1つはResources.en.resx(または特定の場合は.en-GB.resx)。私の実装では、NL(オランダ語)言語をデフォルトの言語として使用しているため、最初のファイルには英語が、2番目のファイルには英語が使用されます。
セットアップは次のようになります。
Resources.resxのプロパティは次のとおりである必要があります:
カスタムツールの名前空間がプロジェクトの名前空間に設定されていることを確認してください。これは、WPFでは、XAML内でResources
を参照できないためです。
そして、リソースファイル内で、アクセス修飾子をPublicに設定します。
別のプロジェクトで使用する
プロジェクトへの参照:[参照]-> [参照の追加]-> [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>
_
または、usingを定義したくない場合は、名前空間全体を入力するだけですOR /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実装ソースチュートリアル: Awesome tutorial blog
モデルのクラスライブラリでの使用
バックエンドの使用方法も同じですが、属性の使用例にすぎません
_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; }
}
}
_
Reshaperがある場合、指定されたリソース名が存在するかどうかが自動的に確認されます。型の安全性を好む場合は、 T4テンプレートを使用してenum を生成できます
もちろん、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;
}
}
}
_
Angular(JSONに変換))で使用する
最近では、Angularのようなフレームワークをコンポーネントと組み合わせてcshtmlなしで使用するのがより一般的です。翻訳はjsonファイルに保存されます。これをJSONファイルに変換したい場合は、非常に簡単です。リソースファイルをjsonファイルに変換するT4テンプレートスクリプトを使用します。 T4 editor をインストールして構文を読み取り、いくつかの変更を行う必要があるため、正しく使用してください。
注意すべきことは1つだけです。データを生成、コピー、データをクリーンアップし、別の言語用に生成することはできません。したがって、以下のコードを使用している言語と同じ回数だけコピーし、「// choose language here」の前にエントリを変更する必要があります。現在、これを修正する時間はありませんが、おそらく後で更新されます(興味がある場合)。
パス:MyProject.Language/T4/CreateWebshopLocalizationEN.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/CreateWebshopLocalizationEN.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);
}
#>
_
これで、すべてのプロジェクトで1つのリソースファイルを使用できるようになりました。これにより、すべてをexclドキュメントに非常に簡単にエクスポートし、誰かがそれを翻訳して再度インポートできるようになります。
多くの異なるアプローチを使用して実装されたプロジェクトを見てきましたが、それぞれに長所と短所があります。
あなたが選んだリソースの方法は非常に理にかなっていると思います。このようなことをするより良い方法があるのではないかとよく思うので、他の人の答えも見るのは面白いでしょう。 SOの1つ など、リソースの使用方法を指す多くのリソースを見てきました。
「最善の方法」があるとは思わない。それは本当にあなたが構築しているアプリケーションの技術とタイプに依存します。
他のポスターが示唆しているように、Webアプリはデータベースに情報を保存できますが、別のリソースファイルを使用することをお勧めします。つまり、リソースファイルはソースとは別です。リソースファイルを分離すると、同じファイルの競合が減り、プロジェクトが成長するにつれて、ローカライズがビジネスロジックとは別に行われることがあります。 (プログラマーおよび翻訳者)。
Microsoft WinFormとWPFの達人は、各ロケールにカスタマイズされた個別のリソースアセンブリの使用を推奨しています。
UI要素をコンテンツに合わせてサイズ調整するWPFの機能により、必要なレイアウト作業が低下します(例:日本語の単語は英語よりもはるかに短い)。
WPFを検討している場合: このmsdnの記事を読むことをお勧めします 正直に言うと、WPFのローカライズツールであるmsbuild、locbaml(およびExcelスプレッドシート)を使用するのは面倒ですが、機能します。
少しだけ関連するもの:私が直面する一般的な問題は、エラーコードではなく、エラーメッセージ(通常は英語)を送信するレガシーシステムを統合することです。これにより、レガシーシステムへの変更、またはバックエンド文字列を自分のエラーコードにマッピングしてからローカライズされた文字列へのマッピングのいずれかが強制されます。 エラーコードはローカライズの友達です
+1データベース
データベースの修正が行われた場合、アプリのフォームはその場で再翻訳することもできます。
すべてのコントロールがXMLファイル(フォームごとに1つ)で言語リソースIDにマップされるシステムを使用しましたが、すべてのIDはデータベースにありました。
基本的に、各コントロールにIDを保持させる代わりに(インターフェイスの実装、またはVB6のタグプロパティの使用)、. NETでは、コントロールツリーはリフレクションを通じて簡単に発見できるという事実を使用しました。読み込まれたフォームが存在しない場合、XMLファイルを構築するプロセス。 XMLファイルはコントロールをリソースIDにマップするため、これを入力してデータベースにマップするだけで済みました。これは、タグ付けされていない場合、または別のIDに分割する必要がある場合、コンパイルされたバイナリを変更する必要がないことを意味しますディクショナリ内で再利用されませんが、IDの初期割り当て時にこれを発見できない場合があります)。しかし、実際には、翻訳プロセス全体がバイナリから完全に独立します(すべてのフォームは、それ自体とそのすべてのコントロールを翻訳する方法を知っている基本フォームから継承する必要があります)。
アプリがより複雑になるのは、挿入ポイントを持つフェーズが使用される場合のみです。
データベース翻訳ソフトウェアは、欠落している翻訳などの処理を容易にするさまざまなワークフローオプションを備えた基本的なCRUDメンテナンス画面でした。
Sisulizer のような市販のツールを使用できます。各言語のサテライトアセンブリが作成されます。注意が必要なのは、フォームクラス名を難読化しないことだけです(難読化ツールを使用する場合)。
私は探していましたが、これを見つけました:
WPFまたはSilverlightを使用している場合、多くの理由で WPF LocalizationExtension を使用できます。
ITのオープンソース無料(無料)は真のスターベル状態です
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);
}
}
そして、私はウェップページでアプローチが同じかもしれないと思います。
がんばろう!
複数のリソースファイルを使用します。設定するのはそれほど難しくないはずです。実際、最近、フォーム言語リソースファイルと組み合わせてグローバル言語ベースのリソースファイルを設定することに関する同様の質問に答えました。
少なくともWinForm開発には最善のアプローチだと思います。
多言語サポートにカスタムプロバイダーを使用し、すべてのテキストをデータベーステーブルに入れます。 Webアプリケーションを更新せずにデータベース内のテキストを更新するときにキャッシュの問題に直面する場合があることを除いて、うまく機能します。
ほとんどのオープンソースプロジェクトでは、この目的で GetText を使用しています。以前に.Netプロジェクトでどのように使用されたか、またそれが使用されたかどうかはわかりません。
標準リソースファイルの方が簡単です。ただし、ルックアップテーブルなどの言語依存データがある場合は、2つのリソースセットを管理する必要があります。
まだやっていませんが、次のプロジェクトではデータベースリソースプロバイダーを実装します。私はMSDNでそれを行う方法を見つけました:
http://msdn.Microsoft.com/en-us/library/aa905797.aspx
私もこの実装を見つけました: