
1. 项目概述为什么PropertyNames.ini值得深挖如果你在UE5项目里做过本地化尤其是涉及C代码的文本翻译大概率遇到过这个场景你在代码里写了一句FText::FromString(TEXT(“Hello World”))然后信心满满地打开本地化工具却发现工具根本找不到这个“Hello World”字符串无法提取出来进行翻译。或者你发现翻译后的文本在运行时其“键”Key是一串难以理解的、由命名空间和标识符组成的哈希值而不是你期望的、有意义的字符串。这些问题十有八九都跟一个不起眼的配置文件——PropertyNames.ini——有关。这个文件是UE5本地化系统Localization中一个非常核心但文档稀少的“元数据”配置文件。它不像Game.ini或DefaultEngine.ini那样频繁露面却直接决定了引擎如何扫描、识别和归类你代码中那些需要被翻译的文本属性。简单来说它是一份“寻人启事”告诉本地化工具“嘿当你去C头文件里找需要翻译的字符串时你应该关注哪些类型的属性以及这些属性可能叫什么名字。”我接手过一个从UE4迁移到UE5的大型项目本地化工作一度陷入混乱。新写的UI文本死活提取不出来而老的翻译键名变得面目全非导致已有的翻译大量失效。经过一番痛苦的排查最终发现症结就在于迁移过程中忽略了对PropertyNames.ini的适配和深入理解。自那以后我就养成了在项目启动或升级引擎时优先审视这个文件的习惯。今天我就结合UE5.3的源码带你彻底拆解这个文件让你不仅能解决眼前的问题更能理解其背后的设计哲学从而在未来的开发中游刃有余。2. 核心机制PropertyNames.ini如何驱动本地化收集要理解PropertyNames.ini我们必须先快速回顾一下UE5本地化收集Gather的基本流程。这个过程通常由命令行工具或编辑器功能触发核心任务是扫描项目中的所有源代码和资源文件找出所有标记为需要本地化的文本FText或FString属性并为它们生成唯一的键和对应的源文本。2.1 收集流程中的角色定位本地化收集器Localization Gatherer在扫描C头文件.h时面对的是一个充满各种宏如UPROPERTY、UFUNCTION和复杂类型的森林。它不可能也不应该去理解每一行代码的语义。它的工作模式更像是“模式匹配”。PropertyNames.ini在这里扮演了“模式匹配规则手册”的角色。收集器的工作简化流程如下解析头文件使用类似编译器的词法/语法分析器将头文件解析成抽象语法树AST的简化版识别出类、结构体、属性声明等。匹配属性声明对于每一个识别出的属性变量收集器会检查它的类型和元数据Metadata。查阅规则手册PropertyNames.ini收集器将属性的类型和可能的元数据关键字与PropertyNames.ini中定义的规则进行比对。决策与提取如果匹配到某条规则并且该规则指示需要本地化Localizabletrue收集器就会将这个属性的“默认值”或指定的元数据值作为一个待翻译的文本单元提取出来。生成定位信息同时收集器会记录这个文本出现在哪个文件、哪个类、哪个属性中这些信息最终会体现在本地化资源的“键”或上下文信息中。如果没有PropertyNames.ini收集器就不知道FText类型的UPROPERTY需要被提取更不用说那些通过元数据DisplayName或ToolTip来提供UI文本的FString属性了。2.2 文件位置与加载时机PropertyNames.ini通常位于引擎目录下[UE5_Install_Path]\Engine\Config\Localization\PropertyNames.ini你也可以在项目目录的Config\Localization\文件夹下创建或覆盖同名文件以实现项目特定的规则。引擎在启动本地化收集流程时会按照标准的配置加载顺序引擎默认 - 项目覆盖来合并这些规则。注意直接修改引擎目录下的文件不是好习惯这会导致引擎升级时你的修改被覆盖。最佳实践是在你的项目Config\Localization\目录下创建自己的PropertyNames.ini文件只写入你需要新增或修改的规则。引擎的配置系统会自动处理覆盖逻辑。3. 源码结构深度解读理论讲完了我们直接上硬菜——看源码。UE5.3中与PropertyNames.ini相关的核心代码主要在Engine\Source\Runtime\Core\Private\Internationalization\TextLocalizationResourceGenerator.cpp和Engine\Source\Runtime\Core\Public\Internationalization\TextProperty.h等文件中。但最直观的入口是解析这个配置文件本身的逻辑。我们可以通过搜索PropertyNames.ini这个字符串在源码中的使用来定位。关键的处理类通常是FLocalizationConfigurationScript或FTextLocalizationResourceGenerator。不过对于我们理解其格式和含义直接分析一个实际的PropertyNames.ini文件内容更为高效。下面是一个基于UE5.3引擎版本的典型文件结构节选和解读[/Script/Engine.Engine] PropertyNamesDisplayName PropertyNamesToolTip这是一个最常见的规则块。我们来拆解它的每一个部分[/Script/Engine.Engine]这是一个“节”Section的头。它不是指某个具体的C类而是一个“配置对象”的路径。这里的/Script/Engine.Engine可以理解为引擎全局配置的命名空间。更重要的是这个节名定义了一条规则的作用范围。收集器在处理一个属性时会看这个属性所属的类或结构体是否“匹配”这个节所描述的规则。匹配逻辑通常不是精确的类名匹配而是更灵活的。 实际上节名中的“类名”部分如Engine用于匹配属性的类型或其外层类的类型。这使得你可以为某一类特定的属性如所有AActor派生类的DisplayName定义规则。PropertyNamesDisplayName这是一条规则项。符号表示“添加”这是UE配置系统的语法用于向一个数组类型的配置项添加元素。PropertyNames是配置项的名称它指定了需要关注的属性名称。注意这里的“属性名称”指的是属性变量本身的名称而是指其“元数据”Metadata的关键字或特殊的标识符。DisplayName是属性名称的值。当收集器发现一个属性拥有名为DisplayName的元数据时就会触发这条规则。那么这条规则合起来的意思是对于任何匹配到本规则作用范围/Script/Engine.Engine的属性如果该属性拥有一个DisplayName元数据则收集器应该尝试将DisplayName元数据对应的字符串值提取出来作为待本地化的文本。3.1 规则项的完整语法与参数一条完整的规则项远比上面的例子复杂。在源码中它通常被解析成一个结构体包含以下关键字段PropertyNames(NameDisplayName, CategoryEditor, Localizabletrue, Searchabletrue, IncludeParentClassestrue)让我们逐一解读每个参数Name(必需)规则所针对的“属性名称”。如前所述这通常是元数据关键字如DisplayName,ToolTip也可以是特殊的标识符如FText用于直接匹配FText类型的属性本身。Category(可选)类别。这个信息会被附加到收集到的文本条目上用于在本地化管理工具如Localization Dashboard中对文本进行分类筛选。例如所有CategoryEditor的文本可能都是编辑器UI用的而CategoryGame的是游戏内文本。这纯粹是一个组织性字段。Localizable(可选默认true)核心参数。布尔值指示匹配到的字符串是否应该被本地化。如果设置为false收集器会忽略这个文本。什么情况下会设为false例如某些ToolTip可能是纯技术描述如“单位厘米”在任何语言下都不需要改变就可以标记为Localizablefalse。Searchable(可选默认true)布尔值指示该文本是否应被纳入可搜索的索引中。这主要影响本地化工具内部的搜索功能。通常保持默认即可。IncludeParentClasses(可选默认值依情况而定)布尔值。这是一个非常重要的参数。它控制规则的继承性。当它为true时这条规则不仅作用于直接匹配节名所指定类的属性也作用于该类的所有派生类子类的属性。当它为false时规则仅精确作用于指定的类。 例如如果你为[/Script/CoreUObject.Object]定义了一条DisplayName规则且IncludeParentClassestrue那么UE中几乎所有的UObject派生类的DisplayName元数据都会被收集因为Object是基类。这通常是你想要的效果。但如果你只想为某个特定的、非派生的工具类比如一个独立的工具类UMyStandaloneTool定义规则就可以设为false。3.2 特殊规则直接匹配FText类型除了匹配元数据PropertyNames.ini还有一个至关重要的功能直接匹配FText类型的属性。这是如何做到的呢答案在于一个特殊的Name值[/Script/CoreUObject.Property] PropertyNames(NameFText, Localizabletrue)这条规则非常强大节[/Script/CoreUObject.Property]Property是UE属性系统UProperty的基类。这个节名非常宽泛意图匹配所有类型的属性。NameFText这不是一个元数据关键字而是一个类型名。当收集器遇到一个属性并且该属性的类型是FText时就会触发这条规则。Localizabletrue指示收集器提取这个FText属性本身的值例如在UPROPERTY初始化列表或构造函数中赋予的默认FText值。这是为什么你代码中的FText类型UPROPERTY能够被自动收集并本地化的根本原因如果没有这条规则或者你错误地修改了它你的所有FText属性都将从本地化雷达上消失。实操心得在自定义项目规则时永远不要在项目的PropertyNames.ini中覆盖或删除引擎默认的[/Script/CoreUObject.Property]节中关于FText的规则。你可以在项目配置中添加新的节和规则但不要动这个根基。我见过有团队为了“清理”配置误删了这条导致整个项目的UI文本都无法本地化排查了整整两天。4. 实战自定义规则解决真实问题理解了原理和语法我们来看几个实战案例看看如何通过自定义PropertyNames.ini来解决实际开发中的痛点。4.1 案例一为自定义UI组件添加ToolTip本地化假设你开发了一个自定义的UMyAwesomeButton组件它有一个FString类型的ButtonToolTip属性你希望这个属性的默认值能被本地化工具收集。未配置前你在头文件中这样定义UCLASS() class UMyAwesomeButton : public UButton { GENERATED_BODY() public: UPROPERTY(EditDefaultsOnly, CategoryAppearance, meta(ToolTipThis is the default tooltip for my awesome button.)) FString ButtonToolTip; };运行本地化收集命令后你会发现“This is the default tooltip...”这个字符串并没有被提取出来。因为默认的PropertyNames.ini规则只匹配通用的ToolTip元数据且作用范围可能不包含你的自定义类。解决方案在你的项目Config\Localization\PropertyNames.ini文件中添加如下规则[/Script/MyGame.MyAwesomeButton] PropertyNames(NameToolTip, CategoryGameUI, Localizabletrue, IncludeParentClassesfalse)节名/Script/MyGame.MyAwesomeButton精确匹配你的类。MyGame是你的模块名。规则匹配名为ToolTip的元数据分类到GameUI需要本地化。IncludeParentClassesfalse因为我们只希望这条规则精确作用于UMyAwesomeButton类不影响其他可能继承自UButton的类除非它们也明确需要此规则。添加此规则并重新运行收集命令后ButtonToolTip属性的ToolTip元数据值就会被成功提取。4.2 案例二排除特定属性的本地化有时某些属性虽然有DisplayName但其值是固定的、不应翻译的。例如一个表示单位的属性UPROPERTY(EditAnywhere, CategoryPhysics, meta(DisplayNameDistance Unit)) FString Unit TEXT(meter);这里的“meter”作为单位在任何语言环境下都应保持为“meter”或“米”而不应被翻译成其他语言的长度单位。解决方案为这个特定的类或属性类型创建一条Localizablefalse的规则。但更精准的做法是利用Category和更具体的节名。不过PropertyNames.ini的匹配粒度是“类元数据名”无法精确到某个具体属性变量。因此更常见的做法是在代码层面避免给不需要本地化的文本使用FText类型或本地化相关的元数据。对于这个例子更好的设计是使用FString且不添加DisplayName元数据或者使用一个枚举类型。然而如果你确实需要为一个广泛使用的元数据如ToolTip在某个特定类上禁用本地化可以这样做[/Script/MyGame.MyPhysicsComponent] PropertyNames(NameToolTip, Localizablefalse)这表示在MyPhysicsComponent类中所有ToolTip元数据都不参与本地化。4.3 案例三处理第三方插件或复杂继承链当你集成一个第三方插件或者你的类继承自一个复杂的引擎类时默认的规则可能不生效或者生效了但产生了你不期望的副作用。策略首先检查插件的文档看看插件是否提供了自己的PropertyNames.ini配置片段。使用IncludeParentClasses进行控制如果你希望规则只应用于你的直接类就设为false。如果你希望影响所有子类就设为true。理解你的类继承层次至关重要。从宽到窄测试可以先定义一个作用范围较宽的规则如针对基类观察收集结果。如果收集了太多不需要的文本再逐步收窄范围指定更具体的子类。本地化收集命令通常有输出日志可以详细查看每个文本被收集的来源文件、行号、类、属性这是调试的黄金信息。5. 调试与验证确保你的规则生效配置了PropertyNames.ini之后如何验证它是否按预期工作5.1 使用命令行工具收集最可靠的方式是使用UE附带的命令行工具进行本地化收集并查看详细输出。打开命令行导航到你的UE5引擎的Engine\Binaries\DotNET\UnrealBuildTool目录或确保UnrealBuildTool在系统路径中。运行收集命令。命令格式通常如下具体参数需参考项目设置UnrealBuildTool.exe -ModeGatherText -ConfigPath/To/Your/Project.uproject或者使用更现代的UnrealEditor-Cmd.exeUnrealEditor-Cmd.exe Path/To/Your/Project.uproject -runGatherText在命令输出中搜索你的目标字符串或类名。成功的收集会输出类似这样的日志LogTextLocalizationResourceGenerator: Display: Gathering text from source file: MyAwesomeButton.h LogTextLocalizationResourceGenerator: Display: Found localized text: This is the default tooltip for my awesome button. (Namespace: MyGame, Key: [Hash...], Source: MyAwesomeButton.h - UMyAwesomeButton::ButtonToolTip [ToolTip])如果没找到检查日志中是否有警告或错误并确认你的PropertyNames.ini文件被正确加载引擎启动日志会显示加载的配置文件。5.2 在编辑器中检查本地化仪表板在Unreal Editor中打开“窗口” - “本地化仪表板”。在“收集”标签页确保你的目标文化如中文已被选中。点击“从文本中收集”按钮。收集完成后切换到“翻译”标签页。在搜索框中输入你期望被收集的字符串片段或类名。如果能搜索到并且其“源”信息正确指向你的类和属性则说明规则生效。5.3 常见排查点如果规则不生效请按以下顺序检查文件位置确认你的自定义PropertyNames.ini文件位于YourProject/Config/Localization/目录下。文件编码确保文件是UTF-8编码无BOM头。Windows记事本保存时默认可能是带BOM的UTF-8这有时会导致解析问题。建议使用VS Code、Notepad等编辑器保存为UTF-8无BOM。语法错误检查INI文件语法括号、引号是否成对逗号分隔是否正确。一个错误的逗号或缺失的引号可能导致整条规则甚至整个文件被忽略。节名匹配确认你写的节名/Script/Module.ClassName中的模块名(Module)和类名(ClassName)完全正确。类名是C类名如MyAwesomeButton而不是蓝图显示名。属性名匹配确认Name字段的值与代码中元数据的关键字完全一致大小写敏感。继承与覆盖如果你在项目配置中写了与引擎默认配置中相同的节和规则项目配置会覆盖引擎配置。确保你的覆盖是故意的并且没有意外地禁用了关键规则如对FText的匹配。重启编辑器/重新生成项目文件有时配置文件的更改需要重启编辑器或者需要重新运行GenerateProjectFiles脚本对于Visual Studio解决方案才能被构建系统完全识别。6. 高级话题与源码宏和元数据的协同PropertyNames.ini并非孤立的它与你在C代码中使用的宏和元数据紧密相关。理解这种协同关系能让你更好地设计可本地化的代码。6.1 LOCTEXT宏与命名空间对于在代码中硬编码的FText最佳实践是使用LOCTEXT宏FText MyText LOCTEXT(“MyKey”, “Hello World”);LOCTEXT宏会自动为字符串“Hello World”创建一个唯一的键并将其归属到一个“命名空间”Namespace下。这个命名空间通常由LOCTEXT_NAMESPACE宏定义。本地化收集器会专门处理这些宏这个过程不完全依赖于PropertyNames.ini。PropertyNames.ini主要处理的是属性UPROPERTY上的文本。6.2 元数据Metadata的灵活运用PropertyNames.ini规则匹配的是元数据的关键字。因此你可以通过定义自定义的元数据关键字来触发特定的本地化行为。例如你有一个专门用于任务描述的属性UPROPERTY(EditAnywhere, Category”Quest”, meta(QuestDescription”Find the hidden treasure.”)) FString Description;你可以为此在PropertyNames.ini中添加规则[/Script/MyGame.QuestBase] PropertyNames(Name”QuestDescription”, Category”GameQuest”, Localizabletrue)这样你就创建了一个专用于任务描述的本地化通道与通用的ToolTip或DisplayName分离开便于在本地化管理工具中分类管理。6.3 性能考量PropertyNames.ini的规则数量不宜过多且应尽可能精确。过于宽泛的规则如为基类Object定义大量规则且IncludeParentClassestrue会导致收集器在扫描每一个属性时都要进行大量的规则匹配拖慢收集速度。对于大型项目收集文本本身就是一个耗时操作优化规则集是提升效率的一个小技巧。7. 总结与最佳实践清单经过对源码和实战的剖析我们可以将PropertyNames.ini的精髓总结为以下几点最佳实践敬畏默认配置不要轻易修改引擎自带的PropertyNames.ini。所有项目特定的定制都应放在YourProject/Config/Localization/PropertyNames.ini中。理解核心规则牢记引擎默认配置中[/Script/CoreUObject.Property]节下NameFText的规则。这是FText属性能被自动收集的生命线。精确匹配原则在添加自定义规则时尽量使用精确的类名IncludeParentClassesfalse避免规则意外应用到不相关的类上。善用Category分类为不同的规则设置清晰的Category如“Editor”、“GameUI”、“Gameplay”等。这能极大地方便后期在本地化仪表板中对海量文本进行筛选和管理。代码与配置协同设计在编写C代码时就应考虑到本地化。思考哪些字符串需要翻译并决定是通过FText属性、LOCTEXT宏还是通过元数据如DisplayName来暴露它们。然后相应地设计或补充PropertyNames.ini规则。调试与验证修改配置后务必通过命令行收集或编辑器仪表板来验证规则是否生效文本是否被正确提取。养成查看收集日志的习惯。文档化团队规范如果是在团队中开发应将项目自定义的PropertyNames.ini规则及其用途写入团队的技术文档或代码规范中。确保所有程序员都了解添加新的需要本地化的文本属性时是否需要更新此配置文件。PropertyNames.ini就像UE5本地化系统中的一个精密齿轮虽然小巧隐蔽但一旦错位整个文本翻译流程就可能卡壳。希望这篇近万字的深度解读能帮你把这个齿轮调整到最佳状态让你在应对多语言项目的复杂需求时更加得心应手。毕竟让全世界玩家都看懂你的游戏第一步就是确保引擎能看懂你代码里哪些文字需要被翻译。