PAYATHON 2026

如何在 Tekla Structures Open API 的 WPF UI 中实现本地化?

技术老齐

结论

可以把公共翻译集中放在 Common.WPF.ail 中,由每个插件窗口显式加载。各插件的 .ail 只保留自身的翻译,公共条目维护一份即可。

如果现有 UI 已经使用 Tekla 的 {tsd:Loc ...},优先采用这种方式,改动最少。.resx 也可以用,但它遵循 .NET 的资源查找规则,需要处理 CurrentUICulture、卫星程序集部署和界面刷新,不能直接沿用 .ail 的加载逻辑。

目前缺少具体代码、文件部署位置和 Tekla 版本,还无法确定原因。可以先从下面两种方案入手排查。

为什么单独创建 Common.WPF.ail 没有生效

Tekla 的 WPF 文档说明,默认本地化文件名由包含窗口的程序集名称决定,额外文件可以通过窗口的 Localization 加载。仅引用 Common.WPF.dll,不会自动加载同名的 .ail 文件。参考:Tekla WPF UI 文档

把条目移到 Common.WPF.ail 后,需要检查:

  • 当前插件窗口是否加载了这个文件。
  • 文件是否已部署到代码指定的位置。
  • 公共控件使用的资源键是否与文件中的标识符完全一致。
  • 文件是否在控件首次读取翻译前加载。

每个窗口都有自己的本地化实例。给某个窗口或单独创建的 Localization 实例加载文件,其他窗口不会因此获得这些翻译。参考:WindowBase 属性

方案一:显式加载公共 .ail 文件

1. 拆分翻译条目并部署文件

保留已经验证可用的 .ail 格式和编码,只移动公共条目。例如:

PluginA.dll
PluginA.ail
PluginB.dll
PluginB.ail
Common.WPF.dll
Common.WPF.ail

这里假定 Common.WPF.ail 与 Common.WPF.dll 在同一目录。也可以放在其他目录,但加载路径必须与实际部署位置一致。

将 .ail 作为外部文件复制到输出目录,并纳入安装包。如果只把它嵌入程序集,下面按文件路径加载的代码就无法直接读取。

建议给公共键加上专用前缀,例如 albl_CommonWpf_Apply,减少与插件或 Tekla 内置条目冲突的可能。

2. 在公共库中封装加载方法

在 Common.WPF 项目中封装加载方法:

using System.IO;
using Tekla.Structures.Dialog;

namespace Common.WPF
{
    public static class SharedLocalization
    {
        public static void LoadInto(Localization localization)
        {
            string assemblyPath =
                typeof(SharedLocalization).Assembly.Location;

            string filePath = Path.Combine(
                Path.GetDirectoryName(assemblyPath),
                "Common.WPF.ail");

            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException(
                    "Shared localization file was not found.",
                    filePath);
            }

            localization.LoadAilFile(filePath);
        }
    }
}

根据公共库自身的程序集位置查找文件,可以避免依赖宿主进程的当前工作目录。如果运行环境会复制或重定位程序集,需要按实际部署位置调整路径。

LoadAilFile(string fileName) 支持连续加载多个文件。查找翻译时,会按加载顺序返回第一个匹配项,所以后加载的文件不会自动覆盖先加载的同名键。参考:LoadAilFile 方法

3. 在每个插件窗口创建控件前加载

在现有窗口构造函数中,保留原有的基类、ViewModel 和数据存储初始化,将公共资源加载放在 InitializeComponent() 之前:

Common.WPF.SharedLocalization.LoadInto(this.Localization);

InitializeComponent();

公共控件继续使用 Tekla 的本地化扩展。假设 XAML 已声明:

xmlns:tsd="clr-namespace:Tekla.Structures.Dialog;assembly=Tekla.Structures.Dialog"

控件可以这样写:

<Button Content="{tsd:Loc albl_CommonWpf_Apply}" />

控件所在的位置必须能取得目标 Tekla 窗口的本地化上下文。如果公共控件用于其他窗口或独立应用,则需要由对应宿主提供本地化支持。

可以先直接读取资源,检查查找结果:

string text =
    this.Localization.GetText("albl_CommonWpf_Apply");

如果取不到预期翻译,先检查路径、文件内容、资源键和 Localization.Language。如果读取结果正确,但控件显示错误,再检查 XAML 绑定和控件创建时机。

不要直接照搬 WinForms 示例中的 Localization.Localize(myForm) 来递归翻译 WPF 控件。WPF 示例使用的是 {tsd:Loc ...} 绑定。

为什么修改 Culture 后,.resx 仍显示默认文本

.resx 使用 .NET 的 ResourceManager。未显式指定 Culture 时,字符串查找通常使用 CurrentUICulture;CurrentCulture 主要影响数字、日期等格式。如果目标语言资源不可用,资源管理器会沿回退链继续查找,最终可能返回默认资源。参考:ResourceManager.GetString

可以逐项检查:

排查项可能造成的结果
只修改了 CurrentCulture格式变化,资源语言不变
在工作线程修改 Culture,UI 在另一线程读取资源UI 仍按自己的 Culture 查找
自动生成的资源类已设置固定的 Culture修改线程 Culture 后,资源类仍使用指定语言
缺少目标语言的 Common.WPF.resources.dll回退到默认资源
控件已经读取并保存了字符串Culture 变化后,已有文本不刷新
ResourceManager 指向宿主或其他程序集没有查找 Common.WPF 的资源

使用 x:Static 时尤其要注意:

<Button Content="{x:Static resources:language.Common_Apply}" />

x:Static 取得的是静态属性的值,本身不会建立随语言变化而更新的绑定。修改 Culture 后,已经赋给 Content 的字符串不会自动重新求值。参考:x

Tekla 插件由宿主加载,初始化入口和执行环境与独立程序不同。因此,不能假定独立程序中的启动代码也会在插件中执行。参考:Tekla 插件说明

方案二:为 .resx 显式指定语言和资源程序集

如果公共库还要用于独立 WPF 应用,可以保留 .resx,提供一个显式接受 CultureInfo 的接口,减少对宿主线程设置的依赖。

假定资源文件为:

Resources/language.resx
Resources/language.zh-CN.resx

默认资源的完整名称是 Common.WPF.Resources.language,则可以这样读取:

using System.Globalization;
using System.Resources;

namespace Common.WPF
{
    public static class SharedText
    {
        private static readonly ResourceManager Manager =
            new ResourceManager(
                "Common.WPF.Resources.language",
                typeof(SharedText).Assembly);

        public static string Get(string key, CultureInfo culture)
        {
            return Manager.GetString(key, culture) ?? key;
        }
    }
}

资源基名必须与项目实际生成的名称一致,不包含 .resx 后缀,不能只根据文件名猜测。

插件显式选择语言后,再读取文本:

using System.Globalization;

// 在控件创建后执行;ApplyButton 是示例控件名。
CultureInfo culture = CultureInfo.GetCultureInfo("zh-CN");

ApplyButton.Content =
    Common.WPF.SharedText.Get("Common_Apply", culture);

如果需要跟随 Tekla 的界面语言,应将 Tekla 的语言标识映射为 .NET Culture。例如,chs 对应 zh-CN,enu 对应 en-US。项目支持的其他语言也需要补充映射,并为无法识别的语言定义回退策略。不要把 Tekla 语言标识直接传给 CultureInfo。

构建后还需要部署语言资源,例如:

Common.WPF.dll
zh-CN/
    Common.WPF.resources.dll

保留构建输出中的语言子目录,并确认插件实际采用的程序集加载方式能够找到卫星程序集。如果只复制 Common.WPF.dll,可能会缺少中文资源。

如果需要在窗口打开期间切换语言,应重新读取文本并更新属性。采用 MVVM 时,可以通过 INotifyPropertyChanged 通知绑定刷新。仅改变 Culture 不会刷新界面。

实施时的取舍

如果已有多个插件使用 .ail 和 {tsd:Loc ...},可以先采用公共 .ail 加显式加载的方式:每个窗口加载一次公共文件,公共翻译只维护一份。

如果 Common.WPF 需要脱离 Tekla 使用,.resx 更便于保持公共库的独立性,但插件需要负责语言映射、资源部署和文本刷新。两种方式都应在目标 Tekla 版本中分别验证能否直接取到翻译,以及控件是否显示该翻译,从而区分资源加载问题和界面绑定问题。

备注:内容仅供参考。