| 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. |