Use inline _locComment syntax for locked tokens in en-US WSL.adml (#40544)

The standalone <!-- {Locked="..."} --> XML comments above each <string> are silently ignored by the POMXML parser used by TouchdownBuildTask, so locked tokens were getting translated in every locale (NetworkingMode values "Mirrored"/"None"/"NAT", ".wslconfig" key names, etc.). Move the directives inside each <string> using the inline form the parser actually honors: <string id="X"><!-- _locComment='{Locked="Foo"}' -->...Foo...</string> <string id="NetworkingModeMirrored"><!-- _locComment="{Locked}" -->Mirrored</string> Update validate-localization.py to extract tokens from the inline form so the baseline-token-presence check still catches authoring mistakes. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Ben Hillis committed May 14, 2026 at 17:32 UTC 5bc0e0184eec376760bc51e61a30ce3d63f9be46
2 files changed +55 -75
intune/en-US/WSL.adml
+29 -55
@@ -5,84 +5,58 @@
5 <description>Windows Subsystem for Linux</description>
6 <resources>
7 <stringTable>
8 - <!-- {Locked="Windows Subsystem for Linux"} -->
9 - <string id="WindowsSubsystemForLinux">Windows Subsystem for Linux</string>
8 + <string id="WindowsSubsystemForLinux"><!-- _locComment='{Locked="Windows Subsystem for Linux"}' -->Windows Subsystem for Linux</string>
9
11 - <!-- {Locked="Windows Subsystem for Linux"} -->
12 - <string id="AllowWSL">Allow the Windows Subsystem for Linux</string>
13 - <!-- {Locked="Windows Subsystem for Linux"} -->
14 - <string id="AllowWSLExplain">When set to disabled, this policy disables access to running Linux distributions in the Windows Subsystem for Linux for all users on the machine.</string>
10 + <string id="AllowWSL"><!-- _locComment='{Locked="Windows Subsystem for Linux"}' -->Allow the Windows Subsystem for Linux</string>
11 + <string id="AllowWSLExplain"><!-- _locComment='{Locked="Windows Subsystem for Linux"}' -->When set to disabled, this policy disables access to running Linux distributions in the Windows Subsystem for Linux for all users on the machine.</string>
12
16 - <!-- {Locked="Windows Subsystem for Linux"} -->
17 - <string id="AllowInboxWSL">Allow the Inbox version of the Windows Subsystem for Linux</string>
18 - <!-- {Locked="Windows Subsystem for Linux"}{Locked="WSL"} -->
19 - <string id="AllowInboxWSLExplain">When set to disabled, this policy disables the inbox version (optional component) of the Windows Subsystem for Linux. If this policy is disabled, only the store version of WSL can be used.</string>
13 + <string id="AllowInboxWSL"><!-- _locComment='{Locked="Windows Subsystem for Linux"}' -->Allow the Inbox version of the Windows Subsystem for Linux</string>
14 + <string id="AllowInboxWSLExplain"><!-- _locComment='{Locked="Windows Subsystem for Linux"}{Locked="WSL"}' -->When set to disabled, this policy disables the inbox version (optional component) of the Windows Subsystem for Linux. If this policy is disabled, only the store version of WSL can be used.</string>
15
21 - <!-- {Locked="WSL1"} -->
22 - <string id="AllowWSL1">Allow WSL1</string>
23 - <!-- {Locked="WSL1"}{Locked="WSL2"} -->
24 - <string id="AllowWSL1Explain">When set to disabled, this policy disables WSL1. When disabled, only WSL2 distributions can be used.</string>
16 + <string id="AllowWSL1"><!-- _locComment='{Locked="WSL1"}' -->Allow WSL1</string>
17 + <string id="AllowWSL1Explain"><!-- _locComment='{Locked="WSL1"}{Locked="WSL2"}' -->When set to disabled, this policy disables WSL1. When disabled, only WSL2 distributions can be used.</string>
18
19 <string id="CustomKernelUserSettingConfigurable">Allow custom kernel configuration</string>
27 - <!-- {Locked=".wslconfig"}{Locked="wsl2.kernel"}{Locked="Store WSL"} -->
28 - <string id="CustomKernelExplain">When set to disabled, this policy disables custom kernel configuration via .wslconfig (wsl2.kernel). This policy only applies to Store WSL.</string>
20 + <string id="CustomKernelExplain"><!-- _locComment='{Locked=".wslconfig"}{Locked="wsl2.kernel"}{Locked="Store WSL"}' -->When set to disabled, this policy disables custom kernel configuration via .wslconfig (wsl2.kernel). This policy only applies to Store WSL.</string>
21
22 <string id="CustomSystemDistroUserSettingConfigurable">Allow custom system distribution configuration</string>
31 - <!-- {Locked=".wslconfig"}{Locked="wsl2.systemDistro"}{Locked="Store WSL"} -->
32 - <string id="CustomSystemDistroExplain">When set to disabled, this policy disables custom system distribution configuration via .wslconfig (wsl2.systemDistro). This policy only applies to Store WSL.</string>
23 + <string id="CustomSystemDistroExplain"><!-- _locComment='{Locked=".wslconfig"}{Locked="wsl2.systemDistro"}{Locked="Store WSL"}' -->When set to disabled, this policy disables custom system distribution configuration via .wslconfig (wsl2.systemDistro). This policy only applies to Store WSL.</string>
24
25 <string id="CustomKernelCommandLineUserSettingConfigurable">Allow kernel command line configuration</string>
35 - <!-- {Locked=".wslconfig"}{Locked="wsl2.kernelCommandLine"}{Locked="Store WSL"} -->
36 - <string id="CustomKernelCommandLineExplain">When set to disabled, this policy disables kernel command line configuration via .wslconfig (wsl2.kernelCommandLine). This policy only applies to Store WSL.</string>
26 + <string id="CustomKernelCommandLineExplain"><!-- _locComment='{Locked=".wslconfig"}{Locked="wsl2.kernelCommandLine"}{Locked="Store WSL"}' -->When set to disabled, this policy disables kernel command line configuration via .wslconfig (wsl2.kernelCommandLine). This policy only applies to Store WSL.</string>
27
28 <string id="AllowDebugShell">Allow the debug shell</string>
39 - <!-- {Locked="wsl.exe"}{Locked="debug-shell"}{Locked="Store WSL"} -->
40 - <string id="AllowDebugShellExplain">When set to disabled, this policy disables the debug shell (wsl.exe --debug-shell). This policy only applies to Store WSL.</string>
29 + <string id="AllowDebugShellExplain"><!-- _locComment='{Locked="wsl.exe"}{Locked="debug-shell"}{Locked="Store WSL"}' -->When set to disabled, this policy disables the debug shell (wsl.exe --debug-shell). This policy only applies to Store WSL.</string>
30
31 <string id="NestedVirtualizationUserSettingConfigurable">Allow nested virtualization</string>
43 - <!-- {Locked=".wslconfig"}{Locked="wsl2.nestedVirtualization"}{Locked="Store WSL"} -->
44 - <string id="NestedVirtualizationExplain">When set to disabled, this policy disables nested virtualization configuration via .wslconfig (wsl2.nestedVirtualization). This policy only applies to Store WSL.</string>
32 + <string id="NestedVirtualizationExplain"><!-- _locComment='{Locked=".wslconfig"}{Locked="wsl2.nestedVirtualization"}{Locked="Store WSL"}' -->When set to disabled, this policy disables nested virtualization configuration via .wslconfig (wsl2.nestedVirtualization). This policy only applies to Store WSL.</string>
33
34 <string id="KernelDebugUserSettingConfigurable">Allow kernel debugging</string>
47 - <!-- {Locked=".wslconfig"}{Locked="wsl2.kernelDebugPort"}{Locked="Store WSL"} -->
48 - <string id="KernelDebugExplain">When set to disabled, this policy disables kernel debugging configuration via .wslconfig (wsl2.kernelDebugPort). This policy only applies to Store WSL.</string>
35 + <string id="KernelDebugExplain"><!-- _locComment='{Locked=".wslconfig"}{Locked="wsl2.kernelDebugPort"}{Locked="Store WSL"}' -->When set to disabled, this policy disables kernel debugging configuration via .wslconfig (wsl2.kernelDebugPort). This policy only applies to Store WSL.</string>
36
37 <string id="CustomNetworkingUserSettingConfigurable">Allow custom networking configuration</string>
51 - <!-- {Locked=".wslconfig"}{Locked="wsl2.networkingMode"}{Locked="Store WSL"} -->
52 - <string id="CustomNetworkingExplain">When set to disabled, this policy disables custom networking configuration via .wslconfig (wsl2.networkingMode). This policy only applies to Store WSL.</string>
38 + <string id="CustomNetworkingExplain"><!-- _locComment='{Locked=".wslconfig"}{Locked="wsl2.networkingMode"}{Locked="Store WSL"}' -->When set to disabled, this policy disables custom networking configuration via .wslconfig (wsl2.networkingMode). This policy only applies to Store WSL.</string>
39
40 <string id="FirewallUserSettingConfigurable">Allow user setting firewall configuration</string>
55 - <!-- {Locked=".wslconfig"}{Locked="wsl2.firewall"}{Locked="Store WSL"} -->
56 - <string id="FirewallExplain">When set to disabled, this policy disables firewall configuration via .wslconfig (wsl2.firewall). This policy only applies to Store WSL.</string>
41 + <string id="FirewallExplain"><!-- _locComment='{Locked=".wslconfig"}{Locked="wsl2.firewall"}{Locked="Store WSL"}' -->When set to disabled, this policy disables firewall configuration via .wslconfig (wsl2.firewall). This policy only applies to Store WSL.</string>
42
43 <string id="AllowDiskMount">Allow passthrough disk mount</string>
59 - <!-- {Locked="WSL2"}{Locked="wsl.exe"}{Locked="mount"}{Locked="Store WSL"} -->
60 - <string id="AllowDiskMountExplain">When set to disabled, this policy disables passthrough disk mounting in WSL2 (wsl.exe --mount). This policy only applies to Store WSL.</string>
44 + <string id="AllowDiskMountExplain"><!-- _locComment='{Locked="WSL2"}{Locked="wsl.exe"}{Locked="mount"}{Locked="Store WSL"}' -->When set to disabled, this policy disables passthrough disk mounting in WSL2 (wsl.exe --mount). This policy only applies to Store WSL.</string>
45
46 <string id="DefaultNetworkingMode">Configure default networking mode</string>
63 - <!-- {Locked="WSL2"} -->
64 - <string id="DefaultNetworkingModeExplain">This policy specifies the default networking mode to be used for WSL2.</string>
65 - <!-- {Locked="None"} -->
66 - <string id="NetworkingModeNone">None</string>
67 - <!-- {Locked="NAT"} -->
68 - <string id="NetworkingModeNAT">NAT</string>
69 - <!-- {Locked="Mirrored"} -->
70 - <string id="NetworkingModeMirrored">Mirrored</string>
71 - <!-- {Locked="VirtioProxy"} -->
72 - <string id="NetworkingModeVirtioProxy">VirtioProxy</string>
73 -
74 - <!-- {Locked="WSL"} -->
75 - <string id="WSLContainer">WSL container</string>
76 -
77 - <!-- {Locked="WSL"} -->
78 - <string id="AllowWSLContainer">Allow WSL container</string>
79 - <!-- {Locked="WSL"}{Locked="Disabled"} -->
80 - <string id="AllowWSLContainerExplain">This policy controls whether WSL container can be used on this machine. When enabled or not configured, users and Windows applications can run Linux containers via WSL. When set to disabled, WSL container is blocked for all users and Windows apps cannot run Linux containers. Warning: Setting this to 'Disabled' can break Windows apps that depend on Linux containers.</string>
81 -
82 - <!-- {Locked="WSL"} -->
83 - <string id="WSLContainerRegistryAllowlist">Allowlist for WSL container registries</string>
84 - <!-- {Locked="WSL"} -->
85 - <string id="WSLContainerRegistryAllowlistExplain">When enabled, WSL container will only be allowed to pull images from the registries listed here. This affects both the WSL container CLI and all applications using the WSL container API.</string>
47 + <string id="DefaultNetworkingModeExplain"><!-- _locComment='{Locked="WSL2"}' -->This policy specifies the default networking mode to be used for WSL2.</string>
48 + <string id="NetworkingModeNone"><!-- _locComment="{Locked}" -->None</string>
49 + <string id="NetworkingModeNAT"><!-- _locComment="{Locked}" -->NAT</string>
50 + <string id="NetworkingModeMirrored"><!-- _locComment="{Locked}" -->Mirrored</string>
51 + <string id="NetworkingModeVirtioProxy"><!-- _locComment="{Locked}" -->VirtioProxy</string>
52 +
53 + <string id="WSLContainer"><!-- _locComment='{Locked="WSL"}' -->WSL container</string>
54 +
55 + <string id="AllowWSLContainer"><!-- _locComment='{Locked="WSL"}' -->Allow WSL container</string>
56 + <string id="AllowWSLContainerExplain"><!-- _locComment='{Locked="WSL"}{Locked="Disabled"}' -->This policy controls whether WSL container can be used on this machine. When enabled or not configured, users and Windows applications can run Linux containers via WSL. When set to disabled, WSL container is blocked for all users and Windows apps cannot run Linux containers. Warning: Setting this to 'Disabled' can break Windows apps that depend on Linux containers.</string>
57 +
58 + <string id="WSLContainerRegistryAllowlist"><!-- _locComment='{Locked="WSL"}' -->Allowlist for WSL container registries</string>
59 + <string id="WSLContainerRegistryAllowlistExplain"><!-- _locComment='{Locked="WSL"}' -->When enabled, WSL container will only be allowed to pull images from the registries listed here. This affects both the WSL container CLI and all applications using the WSL container API.</string>
60 </stringTable>
61 <presentationTable>
62 <presentation id="DefaultNetworkingMode">
tools/devops/validate-localization.py
+26 -20
@@ -187,12 +187,15 @@ def get_adml_entries(path: str) -> tuple[dict, set]:
187 """Parse an .adml file.
188
189 Returns ({string_id: (value, [locked_tokens])}, {presentation_id, ...}).
190 - Locked tokens are extracted from XML comments of the form
191 - `<!-- {Locked="..."}{Locked="..."} -->` placed immediately before a
192 - `<string>` element. Non-Locked comments are ignored.
190 + Locked tokens are extracted from inline comments inside <string> elements:
191 + `<string id="X"><!-- _locComment='{Locked="..."}' -->...text...</string>`
192 + The whole-string `{Locked}` form is also recognized but contributes no
193 + specific tokens to verify. This is the only form honored by the Touchdown
194 + POMXML parser; standalone comments preceding a <string> are ignored by
195 + Touchdown and so are not recognized here either.
196 """
197 # Parse with a TreeBuilder that preserves comments so we can associate
195 - # {Locked="..."} tokens with the <string> element that follows them.
198 + # {Locked="..."} tokens with the <string> element they belong to.
199 parser = xml.etree.ElementTree.XMLParser(
200 target=xml.etree.ElementTree.TreeBuilder(insert_comments=True))
201 root = xml.etree.ElementTree.parse(path, parser=parser).getroot()
@@ -202,17 +205,21 @@ def get_adml_entries(path: str) -> tuple[dict, set]:
205 raise RuntimeError(f'error: {path} is missing the required <stringTable> element')
206
207 strings = {}
205 - pending_tokens = []
208 for child in string_table:
207 - if child.tag is xml.etree.ElementTree.Comment:
208 - pending_tokens.extend(re.findall(r'\{Locked="([^"]*)"\}', child.text or ''))
209 - elif child.tag == f'{ADML_NS}string':
210 - sid = child.get('id')
211 - if sid is not None:
212 - strings[sid] = (child.text or '', pending_tokens)
213 - pending_tokens = []
214 - else:
215 - pending_tokens = []
209 + if child.tag != f'{ADML_NS}string':
210 + continue
211 + sid = child.get('id')
212 + if sid is None:
213 + continue
214 + # The string value is the text before the first child, plus the tail of
215 + # any inline comment children (which is where the actual visible text
216 + # ends up when the comment precedes it inside the <string>).
217 + value = (child.text or '') + ''.join((c.tail or '') for c in child)
218 + tokens = []
219 + for c in child:
220 + if c.tag is xml.etree.ElementTree.Comment:
221 + tokens.extend(re.findall(r'\{Locked="([^"]*)"\}', c.text or ''))
222 + strings[sid] = (value, tokens)
223
224 presentation_table = root.find(f'.//{ADML_NS}presentationTable')
225 if presentation_table is None:
@@ -269,12 +276,11 @@ def validate_adml(adml_folder: str, baseline_language: str) -> bool:
276 result = False
277
278 # Note: we intentionally do not enforce that baseline {Locked="..."}
272 - # tokens appear in translated ADML strings. The Touchdown pipeline does
273 - # not honor `<!-- {Locked="..."} -->` XML comments in .adml files (it
274 - # only honors the `<comment>` element used by .resw), so locked tokens
275 - # are routinely translated. Failing CI here would block every nightly
276 - # localization PR. The baseline check above still catches authoring
277 - # mistakes in en-US.
279 + # tokens appear in translated ADML strings. Translated locale files
280 + # generated before the en-US source migrated to inline _locComment
281 + # directives still contain translated tokens; failing CI here would
282 + # block every nightly localization PR until those caches refresh. The
283 + # baseline check above still catches authoring mistakes in en-US.
284 for sid in baseline_ids & set(translated.keys()):
285 _, tokens = baseline[sid]
286 tvalue, _ = translated[sid]