英文异常信息?

341
我们通过将Exception.Message写入文件来记录系统中发生的任何异常。然而,它们是以客户端的文化方式编写的,土耳其语错误对我来说意义不大。
那么,我们如何在不改变用户文化的情况下记录任何英语错误消息呢?

8
为什么不能像这样切换语言环境:CultureInfo oldCulture = Thread.CurrentThread.CurrentCulture; Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en"); // 在这里抛出异常 => 语言环境是英文 Thread.CurrentThread.CurrentCulture = oldCulture; - CheGueVerra
2
@CheGueVerra 如果我能预测异常发生的时间点,那么我可以在其周围放置线程文化切换,也可以直接解决实际问题。这个问题对于自己抛出的异常是不适用的。 - Nyerguds
19个回答

79
这个问题可以部分解决。框架异常代码从其资源中加载错误消息,根据当前线程的语言环境。在某些异常情况下,这发生在访问Message属性时。
对于这些异常,您可以在记录日志时短暂地将线程语言环境切换为en-US,以获取完整的美式英语消息(先保存原始用户语言环境并立即恢复)。
在单独的线程上执行此操作甚至更好:这确保不会产生任何副作用。例如:
try
{
  System.IO.StreamReader sr=new System.IO.StreamReader(@"c:\does-not-exist");
}
catch(Exception ex)
{
  Console.WriteLine(ex.ToString()); //Will display localized message
  ExceptionLogger el = new ExceptionLogger(ex);
  System.Threading.Thread t = new System.Threading.Thread(el.DoLog);
  t.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
  t.Start();
}

ExceptionLogger类大致如下:

class ExceptionLogger
{
  Exception _ex;

  public ExceptionLogger(Exception ex)
  {
    _ex = ex;
  }

  public void DoLog()
  {
    Console.WriteLine(_ex.ToString()); //Will display en-US message
  }
}

然而,正如Joe在先前版本的评论中正确指出的那样,在抛出异常时,某些消息已经部分地从语言资源中加载。例如,在抛出ArgumentNullException(“foo”)异常时,“参数不能为空”的消息部分本地化,即使使用上述代码,该消息仍将显示为(部分)本地化。
除了使用不切实际的hack(例如在具有en-US区域设置的线程上运行所有非UI代码),似乎没有太多可以做的事情:.NET Framework异常代码没有覆盖错误消息区域设置的功能。

14
您的示例适用于FileNotFoundException,因为当访问Message属性时检索消息资源,而不是在抛出异常时检索。但是对于所有异常都不适用此规则(例如,尝试throw new ArgumentNullException("paramName"))。 - Joe
3
我很困惑。我试着按照你的回答来操作,并想用法语生成我的异常,所以我输入了 t.CurrentUICulture = new System.Globalization.CultureInfo("fr-FR");t.CurrentCulture = new System.Globalization.CultureInfo("fr-FR"); 然而,生成的异常仍然是英文的... - VitalyB
9
本地化的异常文本是.NET框架语言包的一部分。如果您没有安装法语语言包,您将无法获得翻译后的文本。 - Daniel Rose
8
至少在.NET 4.5中,所有的异常都是使用Environment.GetResourceString("...")实例化的,因此您的解决方案已不再适用。最好的方法是抛出自定义异常,并使用自己的(英文)消息文本并使用InnerException属性来保留旧的异常信息。 - webber2k6
1
通过反射获取异常类型名称可能会很方便。 - Guillermo Prandi
显示剩余3条评论

47

或许是一个争议点,但是您可以将文化设置为Invariant而不是en-US。在Invariant文化中,错误消息是用英语显示的。

Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;

它的优点在于不会显得有偏见,特别是对于非美国英语使用区域。 (也就是避免同事的讽刺言辞)


1
我们应该在ASP.NET项目的哪里编写这些代码行?谢谢。 - jason
2
我建议在Application_Start的顶部进行设置。这将使整个项目以英语运行。如果你只是想要错误信息,你可以创建一个封面函数并在每个catch中调用它。 - MPelletier
7
这是否会导致标准消息框按钮也变成英文?这可能不是期望的行为。 - Nyerguds
3
如果你的应用程序完全没有本地化,那当然可以。否则,我认为除了在瞒过愤世嫉俗的同事的情况下削弱所有本地化之外,这种方法没有任何用处。 - Ishmaeel

18
这里有一个解决方案,它不需要任何编码,并且即使是超出我们能够通过代码更改的异常文本(例如在mscorlib中的文本),它也可以工作。
它可能并非在每种情况下都适用(这取决于您的设置,因为您需要能够在主.exe文件旁创建一个.config文件),但对我来说有效。因此,只需在开发中创建一个app.config(或者在生产中创建[myapp].exe.config或web.config),其中包含以下行作为示例:
<configuration>
  ...
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="mscorlib.resources" publicKeyToken="b77a5c561934e089"
                          culture="fr" /> <!-- change this to your language -->

        <bindingRedirect oldVersion="1.0.0.0-999.0.0.0" newVersion="999.0.0.0"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Xml.resources" publicKeyToken="b77a5c561934e089"
                          culture="fr" /> <!-- change this to your language -->

        <bindingRedirect oldVersion="1.0.0.0-999.0.0.0" newVersion="999.0.0.0"/>
      </dependentAssembly>

      <!-- add other assemblies and other languages here -->

    </assemblyBinding>
  </runtime>
  ...
</configuration>

这个做的事情是告诉框架将版本在1到999之间的mscorlibSystem.Xml资源的程序集绑定,以法语(文化设置为“fr”)重定向到一个不存在的程序集(任意版本999)。

因此,当CLR寻找这两个程序集(mscorlib和System.xml)的法语资源时,它将找不到它们并优雅地回退到英语。根据您的上下文和测试,您可能希望将其他包含本地化资源的程序集添加到这些重定向中。

当然,我认为微软不支持这个功能,所以请自行决定是否使用。如果出现问题,您可以删除此配置并检查它与此无关。


1
当需要从测试运行工具中输出英文时可用。 - smg
我尝试了这个,但对我没有用。.net中还有其他资源文件吗?我在哪里可以找到它们? - Tigerware
1
请查看c:\Windows\Microsoft.NET\Framework\v4.0.30319。 每种语言都有一个两个字母的文件夹。请记得将上面的答案中的“fr”替换为实际使用的语言。“no”表示挪威语,“da”表示丹麦语,“sv”表示瑞典语等。 - Wolf5
2
要创建一个完整的列表,请查看该文件夹。大约有120个资源文件。将它们中的每一个添加到配置中。目前似乎这是Windows 10及更高版本的唯一解决方案,因为在较新的Windows中无法卸载.Net语言包(它是操作系统的一部分)。现在甚至放置在GAC中,因此删除这些语言文件夹似乎不起作用。 - Wolf5

12

Windows需要安装您想要使用的UI语言。如果没有安装,它就无法神奇地知道所翻译的消息是什么。

在一个安装了pt-PT语言包的en-US Windows 7 Ultimate中,以下代码:

Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("pt-PT");
string msg1 = new DirectoryNotFoundException().Message;

Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
string msg2 = new FileNotFoundException().Message;

Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fr-FR");
string msg3 = new FileNotFoundException().Message;

生成了pt-PT、en-US和en-US的消息。由于没有安装法语区域文件,它默认使用Windows默认(安装?)语言。


问题已经解决。在我的情况下,安装了大约260MB的英文MUI语言包,并使用Vistalizator程序来优化用户界面。 - Krzysztof Szynter

5

我知道这是一个老话题了,但我认为我的解决方案可能对任何在网上搜索时遇到它的人都很相关:

在异常记录器中,您可以记录ex.GetType.ToString,这将保存异常类的名称。我期望类名应该独立于语言,因此始终以英语表示(例如"System.FileNotFoundException"),尽管目前我没有测试这个想法的外语系统。

如果您真的想要错误消息文本,您可以创建一个字典,其中包含所有可能的异常类名称及其在您喜欢的任何语言中的等效消息,但对于英语而言,我认为类名就足够了。


7
无法工作。我收到了一个InvalidOperationException,由 System.Xml.XmlWellFormedWriter 抛出。你可以猜测具体发生了什么错误,而不必阅读消息。可能有成千上万种不同的情况。 - Nyerguds

5
CultureInfo oldCI = Thread.CurrentThread.CurrentCulture;

Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture ("en-US");
Thread.CurrentThread.CurrentUICulture=new CultureInfo("en-US");
try
{
  System.IO.StreamReader sr=new System.IO.StreamReader(@"c:\does-not-exist");
}
catch(Exception ex)
{
  Console.WriteLine(ex.ToString());
}
Thread.CurrentThread.CurrentCulture = oldCI;
Thread.CurrentThread.CurrentUICulture = oldCI;

不需要绕过任何问题。

谢谢 :)


1
你忘记了分号; - KansaiRobot

4

设置Thread.CurrentThread.CurrentUICulture将用于本地化异常。如果您需要两种类型的异常(一种是用户,一种是您自己),则可以使用以下函数来翻译异常消息。它在.NET库资源中搜索原始文本以获取资源键,然后返回翻译值。但有一个弱点,我还没有找到一个好的解决方案:包含{0}的消息在资源中将无法被找到。如果有人有一个好的解决方案,我会非常感激。

public static string TranslateExceptionMessage(Exception ex, CultureInfo targetCulture)
{
    try
    {
        Assembly assembly = ex.GetType().Assembly;
        ResourceManager resourceManager = new ResourceManager(assembly.GetName().Name, assembly);
        ResourceSet originalResources = resourceManager.GetResourceSet(Thread.CurrentThread.CurrentUICulture, createIfNotExists: true, tryParents: true);
        ResourceSet targetResources = resourceManager.GetResourceSet(targetCulture, createIfNotExists: true, tryParents: true);
        foreach (DictionaryEntry originalResource in originalResources)
            if (originalResource.Value.ToString().Equals(ex.Message.ToString(), StringComparison.Ordinal))
                return targetResources.GetString(originalResource.Key.ToString(), ignoreCase: false); // success

    }
    catch { }
    return ex.Message; // failed (error or cause it's not smart enough to find texts with '{0}'-patterns)
}

如果异常包含格式化参数,那么这种方法是行不通的。 - Nick Berardi
2
是的,正如我所说:“但是有一个弱点我还没有找到好的解决方案:包含{0}的资源消息将无法找到。如果有人有好的解决方案,我会非常感激。” - Vortex852456

3
.NET框架分为两部分:
1. .NET框架本身 2. .NET框架语言包
在.NET框架本身中,所有文本(例如异常消息、MessageBox上的按钮标签等)都是英文的。而语言包则有本地化的文本。
根据您的具体情况,解决方案是卸载语言包(即告诉客户端这样做)。这种情况下,异常文本将是英文的。但需要注意的是,所有其他由框架提供的文本也将是英文的,如MessageBox上的按钮标签、ApplicationCommands的键盘快捷键等。

谢谢!我觉得卸载对话框是用卸载包的语言而不是本地语言,这很讽刺。顺便说一下:语言包似乎每隔几个月就会返回。我还没有搞清楚原因,但我猜测是更新/升级。 - Choco Smith
@ChocoSmith 每次通过Windows Update更新.NET Framework时,语言包都会被重新安装。 - Daniel Rose
6
要求客户卸载他们自己语言的语言包不是可行的解决方案。 - Nyerguds
@Nyerguds 向客户显示系统异常并不是一个可行的解决方案。 - Icet
1
@Icet 不确定这与问题有何关联。没有规定您必须将其显示出来;您可以将其记录在文件中,并向用户显示一个漂亮的消息,告诉他们将日志文件发送给您。但如果该用户恰好是中国人或俄罗斯人,则作为程序员,您最终必须处理中文或俄语错误消息,这才是实际问题。 - Nyerguds

3

基于Undercover1989的回答,但考虑到参数和当消息由几个资源字符串(如参数异常)组成时。

public static string TranslateExceptionMessage(Exception exception, CultureInfo targetCulture)
{
    Assembly a = exception.GetType().Assembly;
    ResourceManager rm = new ResourceManager(a.GetName().Name, a);
    ResourceSet rsOriginal = rm.GetResourceSet(Thread.CurrentThread.CurrentUICulture, true, true);
    ResourceSet rsTranslated = rm.GetResourceSet(targetCulture, true, true);

    var result = exception.Message;

    foreach (DictionaryEntry item in rsOriginal)
    {
        if (!(item.Value is string message))
            continue;

        string translated = rsTranslated.GetString(item.Key.ToString(), false);

        if (!message.Contains("{"))
        {
            result = result.Replace(message, translated);
        }
        else
        {
            var pattern = $"{Regex.Escape(message)}";
            pattern = Regex.Replace(pattern, @"\\{([0-9]+)\}", "(?<group$1>.*)");

            var regex = new Regex(pattern);

            var replacePattern = translated;
            replacePattern = Regex.Replace(replacePattern, @"{([0-9]+)}", @"${group$1}");
            replacePattern = replacePattern.Replace("\\$", "$");

            result = regex.Replace(result, replacePattern);
        }
    }

    return result;
}

谢谢,这似乎可以将一个德语异常翻译成英语异常。 - airo
啊!我在基于 @Vortex852456 的实现创建版本之后才注意到了你的回复。我使用了 LiNQ,使用了Regex.MatchString.Join(Environment.NewLine,...)在结尾处。我还排除了“Globalization.*”资源以避免混淆,你的实现可以从中受益。 - Adam L. S.

1

必须在IIS上进行更改。进入IIS管理器> 选择网站> .NET全球化> 在此处将UI文化设置为英语。

请参阅此其他SO帖子上的更详细答案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接