1
+## Creating Localization Tracking Bugs (GCS Azure DevOps)
2
+
3
+Community members report localization problems in two ways: by filing a GitHub **issue** about a bad
4
+translation, or by opening a **pull request** that edits a localized file (the per-locale UI strings in
5
+`localization/strings/<locale>/Resources.resw`, or the per-locale Intune/group-policy templates in
6
+`intune/<locale>/WSL.adml`), or that adds a brand-new locale file. Both are valid signals and you should
7
+look for **both** when triaging.
8
+
9
+Either way, the fix is handled by the Microsoft Global Collaboration Service (GCS) localization pipeline,
10
+**not** merged directly from GitHub: every non-`en-US` localized file is generated by the GCS team and
11
+regenerated downstream, so any translation edit merged here would be overwritten. (PRs that touch
12
+localized files should be declined with a pointer to `CONTRIBUTING.md`; only the `en-US` sources and the
13
+language-neutral `intune/WSL.admx` live in this repo.) To route a report for review, file a tracking
14
+**Bug** in the GCS Azure DevOps project for each issue or PR.
15
+
16
+> **GCS requirement — be specific: list the exact resource IDs to fix.** The loc team will **not**
17
+> triage abstract reports. A bug that only names the file, language, and a few quoted strings gets
18
+> rejected (moved to the **More Info** state) because the team does not have capacity to parse a vague
19
+> report into the concrete strings that need work — that translation has to be done up front by the
20
+> filer. Every bug must therefore carry an explicit list of the **resource IDs** (the `name` attribute
21
+> of the `<data name="...">` elements in `localization/strings/<locale>/Resources.resw`) that need
22
+> fixing. The gold-standard level of detail is **Bug 1223866** (`.../GlobalCollaborationService/Global%20Collaboration%20Service%20Project/_workitems/edit/1223866`):
23
+> for each resource ID it gives the current (bad) value and the proposed value, e.g.
24
+>
25
+> ```text
26
+> MessageDistroAlreadySet
27
+> current: Der Verteilungsname wurde bereits festgelegt.
28
+> proposed: Der Distributionsname wurde bereits festgelegt.
29
+> ```
30
+
31
+### Deriving the resource-ID list
32
+
33
+How you obtain the IDs depends on the report type:
34
+
35
+- **PR editing an existing locale file** — the changed resource IDs are exactly the `<data name>`
36
+ blocks whose `<value>` differs between the repo's current file and the PR head. Load both and diff
37
+ by name. The PR "Files changed" view already shows current→proposed for each, so post the **compact
38
+ ID list plus a link to the PR diff** rather than re-inlining every value (a 160-row inline table is
39
+ unwieldy; the PR diff is the source of truth for the old/new text).
40
+
41
+ ```powershell
42
+ # changed resource IDs between two Resources.resw files (master vs PR head)
43
+ function Get-ChangedIds($baseResw, $headResw) {
44
+ function Load($p) { $x = [xml](Get-Content $p -Raw); $h = @{}; foreach ($d in $x.root.data) { $h[$d.name] = $d.value }; $h }
45
+ $b = Load $baseResw; $h = Load $headResw
46
+ $h.Keys | Where-Object { $b[$_] -ne $h[$_] }
47
+ }
48
+ ```
49
+
50
+- **Issue citing line numbers** (e.g. `Resources.resw#L185`) — fetch the file **at the exact commit
51
+ the issue links to**, then walk upward from each cited line to the nearest `data name="..."` to get
52
+ the enclosing resource ID. Line numbers drift between commits, so always pin to the linked SHA.
53
+
54
+- **Issue quoting bad strings** — grep the locale `Resources.resw` for each quoted value to find the
55
+ `<data name>` that contains it. One displayed term often maps to several IDs (`.Header`,
56
+ `.Description`, `...TextBox.AutomationProperties.Name`/`.HelpText`, shell-nav `.Content`), so list
57
+ them all.
58
+
59
+- **PR adding a brand-new locale** — this is a net-new whole-file contribution, **not** a correction
60
+ list, so there is nothing pre-existing to "fix". Give the team the actionable file-level items
61
+ instead: (1) hard parity failures vs `en-US` that block `tools/devops/validate-localization.py` —
62
+ placeholder/insert-count mismatches and missing `{Locked="..."}` tokens, listed by resource ID;
63
+ (2) the coverage gap (which `en-US` IDs are missing, and how many of N total); (3) any strings
64
+ present in the file but absent from `en-US` (obsolete — drop them); and (4) that a new locale needs
65
+ build registration, not just a file drop.
66
+
67
+### Coordinates
68
+
69
+| | |
70
+|---|---|
71
+| Org | `https://dev.azure.com/GlobalCollaborationService` |
72
+| Project | `Global Collaboration Service Project` |
73
+| Process | `GCS_Agile` |
74
+| Work item type | `Bug` |
75
+| Web "create from template" link | `https://dev.azure.com/GlobalCollaborationService/Global%20Collaboration%20Service%20Project/_workitems/create/Bug?templateId=b97df4d3-4106-4099-a3a9-1782b5891bec` |
76
+
77
+### Required fields (and known-good values for a community loc PR)
78
+
79
+The Bug type has several required custom picklist fields. Values that work for "community translation PR
80
+needs review":
81
+
82
+| Field (reference name) | Value |
83
+|---|---|
84
+| `System.Title` | `WSL: Review community <locale> localization report (GitHub #<issue or PR>)` |
85
+| `System.Description` / `Microsoft.VSTS.TCM.ReproSteps` | GitHub link + scope + action (see below) |
86
+| `Custom.IssueType` | `Incorrect Translation` |
87
+| `Custom.ProductArea` | `Software` |
88
+| `Custom.How_Found` | `Community` |
89
+| `Custom.Severity_Impact` | `S3 - Medium` (field default) |
90
+| `Microsoft.VSTS.Common.ValueArea` | `Business` (field default) |
91
+| `Custom.HaveyouattachedEnglishandlocalizedscreenshots` | `No` (allowed: `Yes`/`No`; no default — must be set) |
92
+| `Custom.Language` | picklist — see mapping below (the WSL locale code is **not** accepted) |
93
+
94
+Other required custom flags (`Custom.BugBlocked`, `Custom.XLanguage`,
95
+`Custom.OverrideMaxStringsThreshold`, `Custom.SkipSourceAudioFilesValidation`,
96
+`Custom.DidCopilotrespondinthesamelanguage`, `Custom.Copilotattachallfiles`) default to `0` and
97
+auto-fill; leave them unset.
98
+
99
+### `Custom.Language` mapping (picklist values, not locale codes)
100
+
101
+`Custom.Language` is a 148-value picklist keyed by display name. Map the WSL `Resources.resw` locale
102
+folder to the picklist value:
103
+
104
+| WSL locale | `Custom.Language` value |
105
+|---|---|
106
+| `es-ES` | `Spanish (Spain, International Sort)` |
107
+| `zh-CN` | `Chinese (Simplified) - PRC` |
108
+| `el-GR` | `Greek (Greece)` |
109
+| `tr-TR` | `Turkish (Turkey)` |
110
+
111
+For other locales, list the allowed values and match by display name:
112
+
113
+```powershell
114
+$org="https://dev.azure.com/GlobalCollaborationService"
115
+$projEnc="Global%20Collaboration%20Service%20Project"
116
+$res="499b84ac-1321-427f-aa17-267ca6975798" # Azure DevOps AAD resource id
117
+az rest --method get --resource $res `
118
+ --uri "$org/$projEnc/_apis/wit/workitemtypes/Bug/fields/Custom.Language?`$expand=all" `
119
+ --headers "Accept=application/json;api-version=7.1-preview.3" |
120
+ ConvertFrom-Json | Select-Object -ExpandProperty allowedValues
121
+```
122
+
123
+### Suggested description body
124
+
125
+```
126
+Community localization report on GitHub microsoft/WSL needs review by the loc pipeline.
127
+
128
+GitHub: https://github.com/microsoft/WSL/<issues or pull>/<number>
129
+Language: <locale>
130
+Type: <issue reporting a bad translation | PR editing the localized file>
131
+Scope: localized file (localization/strings/<locale>/Resources.resw or intune/<locale>/WSL.adml).
132
+ For a PR: <edits to existing | brand-new>, +A/-B, 1 file. For an issue: the specific string(s) reported.
133
+Resource IDs to fix: <explicit list of <data name> keys - see the dedicated comment with the full
134
+ list, or paste it here>. This is required; see "Deriving the resource-ID list" above.
135
+Action: validate placeholder/locked-token parity vs en-US (tools/devops/validate-localization.py),
136
+then incorporate or advise. New locales additionally need build registration, not just a file drop.
137
+Note: do not merge the GitHub PR - localized files are regenerated by GCS and edits here are overwritten.
138
+```
139
+
140
+The resource-ID list is the part GCS actually needs. If it is long, post it as a work-item **comment**
141
+(ADO comments accept HTML and can be edited in place via `PATCH .../comments/<id>`) and reference that
142
+comment from the description.
143
+
144
+### Creating via CLI (`az boards`)
145
+
146
+Requires the `azure-devops` az extension and an identity with **create/edit work item** permission in
147
+the project (see caveat below).
148
+
149
+```powershell
150
+$org="https://dev.azure.com/GlobalCollaborationService"
151
+$proj="Global Collaboration Service Project"
152
+$area="Global Collaboration Service Project\Global\Windows" # working area path, see Permissions gotcha
153
+$desc="Community es-ES localization PR. PR: https://github.com/microsoft/WSL/pull/14109 ..."
154
+az boards work-item create --org $org --project $proj --type "Bug" `
155
+ --title "WSL: Review community es-ES localization PR (GitHub #14109)" `
156
+ --area "$area" --description $desc `
157
+ --fields "Microsoft.VSTS.TCM.ReproSteps=$desc" `
158
+ "Custom.Language=Spanish (Spain, International Sort)" `
159
+ "Custom.IssueType=Incorrect Translation" `
160
+ "Custom.ProductArea=Software" `
161
+ "Custom.How_Found=Community" `
162
+ "Custom.Severity_Impact=S3 - Medium" `
163
+ "Microsoft.VSTS.Common.ValueArea=Business" `
164
+ "Custom.HaveyouattachedEnglishandlocalizedscreenshots=No"
165
+```
166
+
167
+### Gotchas
168
+
169
+- **`az.cmd` eats `&` in URLs.** When using `az rest`, never put `&` (multiple query params) in
170
+ `--uri` on Windows — cmd truncates the URL there. Pass `api-version` via the **Accept header**
171
+ instead: `--headers "Accept=application/json;api-version=7.1"`, keeping at most one `?param` in the URI.
172
+- **Permissions are area-path-scoped, and the path matters.** Creating work items requires explicit rights on
173
+ the *specific area path*, not the project as a whole. The correct, durable path for WSL localization bugs is
174
+ `Global Collaboration Service Project\Global\Windows` (confirmed against existing manually-filed WSL loc
175
+ bugs). Always set `System.AreaPath` (or `--area`) to that path. A `@ntdev.microsoft.com` identity is denied
176
+ at the project root and most other paths (`TF237111: ... does not have permissions to save work items under
177
+ the specified area path`). Do NOT file under `...\C and AI\Unspecified`: an identity may briefly appear to
178
+ have access there, but bugs filed there get cleaned up and the access is not stable. To probe a path without
179
+ creating anything, POST with `?validateOnly=true`. If your identity lacks rights everywhere, create the bug
180
+ through the web template link above.
181
+- **`System.Description` is required, separately from repro steps.** The Bug type rejects a create with
182
+ `TF401320: Rule Error for field Description ... Required, InvalidEmpty` unless `System.Description` is set.
183
+ Set both `System.Description` and `Microsoft.VSTS.TCM.ReproSteps` (the same body is fine). When POSTing a
184
+ JSON-patch document via `az rest`, use newline-to-`<br>` so the HTML-rendered fields keep their line breaks.
185
+- **Picklist discovery.** Field allowed-values often don't expand through the plain field endpoint; use
186
+ `?$expand=all` on `.../workitemtypes/Bug/fields/<ref>` as shown above.
187
+- **"Not found" can mean "no access," not "doesn't exist."** WIQL and `GET .../workitems/<id>` honor ACLs:
188
+ a `CreatedBy=@me` query returning zero rows, or `TF401232: Work item <id> does not exist, or you do not
189
+ have permissions to read it`, often just means your identity can't read that area path. Don't assume the
190
+ bug was deleted. A gap in sequential work-item IDs is a tell that items were created but are now invisible
191
+ to you (e.g. filed under a path whose access got revoked, then reaped) - which is exactly why you should
192
+ stick to the durable `Global\Windows` path above.
193
+- **Read the bug back after creating it.** Immediately `GET` each new work item id (or open the URL) to
194
+ confirm it's actually persisted and readable under your identity. A create that "succeeds" against a flaky
195
+ area path can vanish later; a read-back catches that while you can still re-file.
196
+- **Non-ASCII gets mangled, and `az` errors poison `ConvertFrom-Json`.** Locale content (Turkish, Greek, CJK)
197
+ comes back cp1252-garbled unless you set `[Console]::OutputEncoding=[System.Text.Encoding]::UTF8` first.
198
+ Also, `az` prints errors to the stream, so piping straight into `ConvertFrom-Json` throws on any failure;
199
+ capture with `Out-String` and parse, or check `$LASTEXITCODE`. Pass JSON-patch bodies via `--body "@file"`
200
+ (content type `application/json-patch+json`) rather than inline to dodge quoting hell.
201
+- **Don't inline huge diffs into a comment.** For a PR that touches many strings, post the resource-ID
202
+ list and link the PR "Files changed" page for the current→proposed values; a 100+ row HTML table
203
+ makes the bug unreadable and can bump against comment-size limits. ADO comments can be edited in
204
+ place (`PATCH .../comments/<id>?api-version=7.1-preview.4`), so trim/replace rather than re-posting.
205
+
206
+### After creating
207
+
208
+Keep the GitHub link in the ADO Bug's description/repro steps (it's already there from the body above) so the
209
+loc team can trace back to the report. Do **not** paste the internal ADO Bug ID into the public GitHub PR or
210
+issue. Then read each new bug back (see gotcha above) to confirm it persisted under `Global\Windows`.
211
+
212
+If GCS moves a bug to **More Info**, it almost always means the resource-ID detail is missing or too
213
+abstract: supply the explicit `<data name>` list (see "Deriving the resource-ID list"), then move the
214
+bug back to **Active** and reassign it to the requesting GCS reviewer.
215
+
216
+### What happens after you file (automated pipeline)
217
+
218
+Filing the Bug under `Global\Windows` is usually the end of the manual work. A **Fabric Self Service** bot
219
+(`FabricClient-Prod`) automatically picks the Bug up and creates a **DTT (Direct-To-Translator) job** for the
220
+locale, posting its progress as Bug discussion comments. So a stream of `FabricClient-Prod` comments is the
221
+**expected, healthy** path — not something a human has to act on:
222
+
223
+- `Bug picked up for processing by the Fabric Self Service process.`
224
+- `Successfully created 1 DTT Jobs: ... <locale> ...` → the report is now in the translation pipeline; **no
225
+ further action**.
226
+
227
+Watch for these exceptions, which **do** need action:
228
+
229
+- **New locale not onboarded.** If the locale isn't onboarded to **TDBuild** for the WSL subtenant
230
+ (`WindowsUndocking` / `LiftedWSL`), the DTT job can't be created and a human comments pointing you to file an
231
+ onboarding request at the **Global Services Hub** (`https://globalservices.powerappsportals.com/global/`).
232
+ This is GCS-side onboarding and is *separate from* (and in addition to) the WSL-repo build registration a new
233
+ locale needs. Greek (`el-GR`, GitHub #40244) hit exactly this.
234
+- **Oversized report.** A non-retriable `Rqf query exceeded threshold of 1000 words` failure means the report's
235
+ scope is too large; reduce the scope or set `Custom.OverrideMaxStringsThreshold` and re-file.
236
+
237
+If you only see `FabricClient-Prod` "Successfully created ... DTT Jobs" comments, you're done.