Skip to content

Commit 7991b7d

Browse files
bschnurrCopilot
andauthored
Use shared localization submission and pull-only release imports (#1134)
* Use shared English localization action and pull-only release imports Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Restore UTF-8 release parameter display names Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Isolate debugger unit tests from live Python environment lookup Stub active environment lookup to prevent report-issue timeouts and launch test activation races. Await parameterized launch assertions and compare generated server-ready actions structurally. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove startup filesystem contention from report-issue unit tests Load real templates and expected fixtures once in suite setup with the existing I/O timeout. Stub exact template reads during command tests so their 2-second timeout no longer depends on asynchronous disk I/O while VS Code starts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use merged Pylance-Eng main for localization releases Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Explain temporary localization dev branch reference Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a362413 commit 7991b7d

7 files changed

Lines changed: 136 additions & 134 deletions

File tree

‎.github/workflows/localization.yml‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Localization
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
paths:
9+
- package.json
10+
- package.nls.json
11+
- src/**/*.ts
12+
- .github/workflows/localization.yml
13+
14+
permissions:
15+
contents: read
16+
17+
concurrency:
18+
group: localization-${{ github.ref }}
19+
cancel-in-progress: false
20+
21+
jobs:
22+
localization:
23+
if: github.repository == 'microsoft/vscode-python-debugger'
24+
runs-on: ubuntu-latest
25+
timeout-minutes: 20
26+
steps:
27+
- uses: microsoft/vscode-common-python-lsp/.github/actions/localization@d1877b84bb10ff7caa6bd12ab4e75498ba760790
28+
with:
29+
app-id: ${{ vars.PYTHON_LOCALIZATION_APP_ID }}
30+
private-key: ${{ secrets.PYTHON_LOCALIZATION_PRIVATE_KEY }}

‎.gitignore‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,8 @@ bundled/libs/
1313
# Generated files (sources live in pre-built/)
1414
l10n/bundle.l10n.*.json
1515
package.nls.*.json
16-
telemetry.json
16+
telemetry.json
17+
18+
# Localization is generated from the dev handback during releases.
19+
/package.nls.*.json
20+
/l10n/

‎README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,15 @@ The DevDiv Azure stable/prerelease pipelines pass the npm proxy to their build/p
132132
Each Python bootstrap and bundled-library install step explicitly sets `PIP_INDEX_URL` from `PIP_PROXY_INDEX_URL`, selecting the pip proxy even when a template's authentication setup changes the job-level index. Release triggers, approval gates, signing, and publishing behavior are unchanged.
133133

134134
Dependabot continues to use public registries and its existing update settings. Legacy Azure stable, prerelease, and validation definitions are unchanged.
135+
136+
### Localization
137+
138+
The **Localization** GitHub workflow submits English strings using the [shared composite action and Python tooling](https://github.com/microsoft/vscode-common-python-lsp#shared-localization-automation). Stable and pre-release DevDiv pipelines independently retrieve translations through Pylance-Eng's `pullLocalization: true`; releases never submit English or create translation-update PRs.
139+
140+
Both paths use `microsoft/vscode-python-extensions-loc`, branch **`dev`**, with canonical extension ID **`ms-python.debugpy`**. English XLF is stored in `src/en/ms-python.debugpy` and handbacks in `out/ms-python.debugpy`. The action and Azure tooling checkout are pinned to the same reviewed revision.
141+
142+
Configure the `PYTHON_LOCALIZATION_APP_ID` Actions variable and `PYTHON_LOCALIZATION_PRIVATE_KEY` secret here, or grant access to organization-level credentials. The app needs Contents write access only to the localization drop; the caller's repository token stays read-only. The shared action uses the Microsoft npm proxy and pinned localization CLI. Authorize the literal `Github-Pylance` service connection for both Azure repository resources and release pipelines.
143+
144+
Keep English `package.nls.json` tracked. Root `package.nls.<locale>.json` and `l10n/` runtime files are generated, git-ignored release outputs. VSCE must include generated locale files despite Git ignores. Local builds are English-only unless translations are imported explicitly (debugger pre-built assets remain available, but the release import runs after their overlay and is authoritative).
145+
146+
Translation is asynchronous. All 14 supported locales must be present, and every required real-language table must contain translation evidence. Individual untranslated strings are allowed. Missing, incomplete, or wholly untranslated handbacks fail before packaging, without falling back to stale translations. Do not publish while onboarding or real translated output is pending.

‎build/azure-devdiv-pipeline.pre-release.yml‎

Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,21 @@ schedules:
1212

1313
resources:
1414
repositories:
15-
- repository: MicroBuildTemplate
16-
type: git
17-
name: 1ESPipelineTemplates/MicroBuildTemplate
18-
ref: refs/tags/release
19-
2015
- repository: templates
2116
type: git
2217
name: DevDiv/Pylance-Eng
2318
ref: refs/heads/main
19+
- repository: translations
20+
type: github
21+
name: microsoft/vscode-python-extensions-loc
22+
# Temporarily use dev while the localization force-push issue is resolved.
23+
ref: refs/heads/dev
24+
endpoint: Github-Pylance
25+
- repository: localizationTools
26+
type: github
27+
name: microsoft/vscode-common-python-lsp
28+
ref: d1877b84bb10ff7caa6bd12ab4e75498ba760790
29+
endpoint: Github-Pylance
2430
variables:
2531
- name: TeamName
2632
value: VSCode-python-debugger
@@ -100,43 +106,16 @@ parameters:
100106
displayName: Build extension
101107

102108
extends:
103-
template: azure-pipelines/MicroBuild.1ES.Official.yml@MicroBuildTemplate
109+
template: azure-pipelines/extension/pre-release.yml@templates
104110
parameters:
105-
sdl:
106-
sourceAnalysisPool: VSEng-MicroBuildVSStable
107-
codeSignValidation:
108-
enabled: true
109-
sbom:
110-
enabled: false # Disable global SBOM generation; we'll enable selectively per artifact output
111-
pool:
112-
name: AzurePipelines-EO
113-
os: windows
114-
115-
customBuildTags:
116-
- ES365AIMigrationTooling
117-
stages:
118-
- stage: Build
119-
displayName: Build & Package Extension
120-
jobs:
121-
- template: azure-pipelines/extension/templates/jobs/package.yml@templates
122-
parameters:
123-
buildPlatforms: ${{ parameters.buildPlatforms }}
124-
buildSteps: ${{ parameters.buildSteps }}
125-
isPreRelease: true
126-
standardizedVersioning: true
127-
customNPMRegistry: $(NPM_CONFIG_REGISTRY)
128-
129-
- stage: Publish
130-
displayName: Publish Extension
131-
dependsOn: Build
132-
jobs:
133-
- template: azure-pipelines/extension/templates/jobs/publish-extension.yml@templates
134-
parameters:
135-
buildPlatforms: ${{ parameters.buildPlatforms }}
136-
publishExtension: ${{ parameters.publishExtension }}
137-
preRelease: true
138-
teamName: $(TeamName)
139-
ghCreateTag: true
140-
ghCreateRelease: true
141-
ghReleaseAddChangeLog: true
142-
customNPMRegistry: $(NPM_CONFIG_REGISTRY)
111+
buildPlatforms: ${{ parameters.buildPlatforms }}
112+
buildSteps: ${{ parameters.buildSteps }}
113+
standardizedVersioning: true
114+
customNPMRegistry: $(NPM_CONFIG_REGISTRY)
115+
publishExtension: ${{ parameters.publishExtension }}
116+
teamName: $(TeamName)
117+
ghCreateTag: true
118+
ghCreateRelease: true
119+
ghReleaseAddChangeLog: true
120+
sourceAnalysisPool: VSEng-MicroBuildVSStable
121+
pullLocalization: true

‎build/azure-devdiv-pipeline.stable.yml‎

Lines changed: 24 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@ pr: none
77

88
resources:
99
repositories:
10-
- repository: MicroBuildTemplate
11-
type: git
12-
name: 1ESPipelineTemplates/MicroBuildTemplate
13-
ref: refs/tags/release
14-
1510
- repository: templates
1611
type: git
1712
name: DevDiv/Pylance-Eng
1813
ref: refs/heads/main
14+
- repository: translations
15+
type: github
16+
name: microsoft/vscode-python-extensions-loc
17+
# Temporarily use dev while the localization force-push issue is resolved.
18+
ref: refs/heads/dev
19+
endpoint: Github-Pylance
20+
- repository: localizationTools
21+
type: github
22+
name: microsoft/vscode-common-python-lsp
23+
ref: d1877b84bb10ff7caa6bd12ab4e75498ba760790
24+
endpoint: Github-Pylance
1925
variables:
2026
- name: TeamName
2127
value: VSCode-python-debugger
@@ -95,58 +101,17 @@ parameters:
95101
displayName: Build extension
96102

97103
extends:
98-
template: azure-pipelines/MicroBuild.1ES.Official.yml@MicroBuildTemplate
104+
template: azure-pipelines/extension/stable.yml@templates
99105
parameters:
100-
sdl:
101-
sourceAnalysisPool: VSEng-MicroBuildVSStable
102-
codeSignValidation:
103-
enabled: true
104-
sbom:
105-
enabled: false # Disable global SBOM generation; we'll enable selectively per artifact output
106-
pool:
107-
name: AzurePipelines-EO
108-
os: windows
109-
110-
customBuildTags:
111-
- ES365AIMigrationTooling
112-
stages:
113-
- stage: Build
114-
displayName: Build & Package Extension
115-
jobs:
116-
- template: azure-pipelines/extension/templates/jobs/package.yml@templates
117-
parameters:
118-
buildPlatforms: ${{ parameters.buildPlatforms }}
119-
buildSteps: ${{ parameters.buildSteps }}
120-
isPreRelease: false
121-
standardizedVersioning: true
122-
customNPMRegistry: $(NPM_CONFIG_REGISTRY)
123-
124-
- stage: WaitForValidation
125-
displayName: Wait for Validation
126-
dependsOn: Build
127-
jobs:
128-
- job: wait_for_validation
129-
displayName: Wait for manual validation
130-
pool: server
131-
steps:
132-
- task: ManualValidation@0
133-
timeoutInMinutes: 1440 # task times out in 1 day
134-
inputs:
135-
notifyUsers: 'plseng@microsoft.com'
136-
instructions: 'please test the latest draft release then publish it.'
137-
onTimeout: 'reject'
138-
139-
- stage: Publish
140-
displayName: Publish Extension
141-
dependsOn: WaitForValidation
142-
jobs:
143-
- template: azure-pipelines/extension/templates/jobs/publish-extension.yml@templates
144-
parameters:
145-
buildPlatforms: ${{ parameters.buildPlatforms }}
146-
publishExtension: ${{ parameters.publishExtension }}
147-
preRelease: false
148-
teamName: $(TeamName)
149-
ghCreateTag: true
150-
ghCreateRelease: true
151-
ghReleaseAddChangeLog: true
152-
customNPMRegistry: $(NPM_CONFIG_REGISTRY)
106+
buildPlatforms: ${{ parameters.buildPlatforms }}
107+
buildSteps: ${{ parameters.buildSteps }}
108+
standardizedVersioning: true
109+
customNPMRegistry: $(NPM_CONFIG_REGISTRY)
110+
publishExtension: ${{ parameters.publishExtension }}
111+
teamName: $(TeamName)
112+
ghCreateTag: true
113+
ghCreateRelease: true
114+
ghReleaseAddChangeLog: true
115+
validationNotifyUsers: plseng@microsoft.com
116+
sourceAnalysisPool: VSEng-MicroBuildVSStable
117+
pullLocalization: true

‎src/test/unittest/common/application/commands/reportIssueCommand.unit.test.ts‎

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,46 @@ import * as Telemetry from '../../../../../extension/telemetry/index';
1212
import { EventName } from '../../../../../extension/telemetry/constants';
1313
import * as vscodeapi from '../../../../../extension/common/vscodeapi';
1414
import * as pythonApi from '../../../../../extension/common/python';
15-
import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../../../constants';
15+
import { EXTENSION_ROOT_DIR_FOR_TESTS, TEST_TIMEOUT } from '../../../../constants';
16+
import { EXTENSION_ROOT_DIR } from '../../../../../extension/common/constants';
1617
import { openReportIssue } from '../../../../../extension/common/application/commands/reportIssueCommand';
1718
import { PythonEnvironment } from '../../../../../extension/envExtApi';
1819

1920
suite('Report Issue Command', () => {
2021
let executeCommandStub: sinon.SinonStub;
2122
let resolveEnvironmentStub: sinon.SinonStub;
23+
const templatePath = path.join(EXTENSION_ROOT_DIR, 'resources', 'report_issue_template.md');
24+
const userTemplatePath = path.join(EXTENSION_ROOT_DIR, 'resources', 'report_issue_user_data_template.md');
25+
let template: string;
26+
let userTemplate: string;
27+
let expectedIssueBody: string;
28+
let expectedData: string;
2229

23-
setup(async () => {
30+
suiteSetup(async function () {
31+
this.timeout(TEST_TIMEOUT);
32+
[template, userTemplate, expectedIssueBody, expectedData] = await Promise.all([
33+
fs.readFile(templatePath, 'utf8'),
34+
fs.readFile(userTemplatePath, 'utf8'),
35+
fs.readFile(
36+
path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src', 'test', 'resources', 'issueTemplate.md'),
37+
'utf8',
38+
),
39+
fs.readFile(
40+
path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src', 'test', 'resources', 'issueUserDataTemplate.md'),
41+
'utf8',
42+
),
43+
]);
44+
});
45+
46+
setup(() => {
47+
const readFileStub = sinon.stub(fs, 'readFile').rejects(new Error('Unexpected template file read'));
48+
readFileStub.withArgs(templatePath, 'utf8').resolves(template);
49+
readFileStub.withArgs(userTemplatePath, 'utf8').resolves(userTemplate);
2450
executeCommandStub = sinon.stub(vscodeapi, 'executeCommand');
51+
sinon
52+
.stub(pythonApi, 'getActiveEnvironmentPath')
53+
.resolves({ id: '/path/to/interpreter', path: '/path/to/interpreter' });
2554
resolveEnvironmentStub = sinon.stub(pythonApi, 'resolveEnvironment');
26-
// Ensure useEnvironmentsExtension is false for these tests
27-
(pythonApi as any).useEnvironmentsExtension = false;
2855
const interpreter = {
2956
envId: {
3057
id: '/path/to/interpreter',
@@ -37,30 +64,12 @@ suite('Report Issue Command', () => {
3764

3865
teardown(() => {
3966
sinon.restore();
40-
(pythonApi as any).useEnvironmentsExtension = undefined;
4167
});
4268

4369
test('Test if issue body is filled correctly when including all the settings', async () => {
4470
executeCommandStub.withArgs('workbench.action.openIssueReporter', sinon.match.any).resolves();
4571
await openReportIssue();
46-
47-
const issueTemplatePath = path.join(
48-
EXTENSION_ROOT_DIR_FOR_TESTS,
49-
'src',
50-
'test',
51-
'resources',
52-
'issueTemplate.md',
53-
);
54-
const expectedIssueBody = fs.readFileSync(issueTemplatePath, 'utf8');
55-
56-
const userDataTemplatePath = path.join(
57-
EXTENSION_ROOT_DIR_FOR_TESTS,
58-
'src',
59-
'test',
60-
'resources',
61-
'issueUserDataTemplate.md',
62-
);
63-
const expectedData = fs.readFileSync(userDataTemplatePath, 'utf8');
72+
sinon.assert.calledOnceWithExactly(resolveEnvironmentStub, '/path/to/interpreter');
6473

6574
sinon.assert.calledOnceWithExactly(executeCommandStub, 'workbench.action.openIssueReporter', sinon.match.any);
6675

‎src/test/unittest/configuration/resolvers/launch.unit.test.ts‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => {
3838
let getOSTypeStub: sinon.SinonStub;
3939
let getWorkspaceFolderStub: sinon.SinonStub;
4040
let getInterpreterDetailsStub: sinon.SinonStub;
41+
let getActiveEnvironmentPathStub: sinon.SinonStub;
4142
let getEnvFileStub: sinon.SinonStub;
4243
let getDebugEnvironmentVariablesStub: sinon.SinonStub;
4344
let getConfigurationStub: sinon.SinonStub;
@@ -48,6 +49,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => {
4849
getWorkspaceFolderStub = sinon.stub(vscodeapi, 'getWorkspaceFolders');
4950
getOSTypeStub.returns(osType);
5051
getInterpreterDetailsStub = sinon.stub(pythonApi, 'getInterpreterDetails');
52+
getActiveEnvironmentPathStub = sinon.stub(pythonApi, 'getActiveEnvironmentPath');
5153
getEnvFileStub = sinon.stub(settings, 'getEnvFile');
5254
getDebugEnvironmentVariablesStub = sinon.stub(helper, 'getDebugEnvironmentVariables');
5355
getConfigurationStub = sinon.stub(vscodeapi, 'getConfiguration');
@@ -87,6 +89,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => {
8789

8890
function setupIoc(pythonPath: string, workspaceFolder?: WorkspaceFolder) {
8991
getInterpreterDetailsStub.resolves({ path: [pythonPath] });
92+
getActiveEnvironmentPathStub.resolves({ id: pythonPath, path: pythonPath });
9093

9194
if (workspaceFolder) {
9295
getEnvFileStub
@@ -868,7 +871,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => {
868871
const pythonFile = 'xyz.py';
869872
setupIoc(pythonPath);
870873
setupActiveEditor(pythonFile, PYTHON_LANGUAGE);
871-
testsForRedirectOutput.forEach(async (testParams) => {
874+
for (const testParams of testsForRedirectOutput) {
872875
const debugConfig = await resolveDebugConfiguration(workspaceFolder, {
873876
...launch,
874877
console: testParams.console as ConsoleType,
@@ -879,7 +882,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => {
879882
expect(debugConfig).to.have.property('debugOptions');
880883
expect((debugConfig as DebugConfiguration).debugOptions).to.contain(DebugOptions.RedirectOutput);
881884
}
882-
});
885+
}
883886
});
884887

885888
test('Test fixFilePathCase', async () => {
@@ -956,13 +959,13 @@ getInfoPerOS().forEach(([osName, osType, path]) => {
956959
uriFormat: '%s',
957960
action: 'openExternally',
958961
};
959-
testsForautoStartBrowser.forEach(async (testParams) => {
962+
for (const testParams of testsForautoStartBrowser) {
960963
const debugConfig = await resolveDebugConfiguration(workspaceFolder, {
961964
...launch,
962965
...testParams,
963966
});
964-
expect(debugConfig).to.have.property('serverReadyAction', expectedServerReadyAction);
965-
});
967+
expect(debugConfig).to.have.deep.property('serverReadyAction', expectedServerReadyAction);
968+
}
966969
});
967970

968971
test('Preserve serverReadyAction when already defined in configuration', async () => {
@@ -978,7 +981,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => {
978981
action: 'debugWithChrome',
979982
};
980983

981-
testsForautoStartBrowser.forEach(async (testParams) => {
984+
for (const testParams of testsForautoStartBrowser) {
982985
const debugConfig = await resolveDebugConfiguration(workspaceFolder, {
983986
...launch,
984987
...testParams,
@@ -995,7 +998,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => {
995998
uriFormat: '%s',
996999
action: 'openExternally',
9971000
});
998-
});
1001+
}
9991002
});
10001003

10011004
test('Send consoleName value to debugpy as consoleTitle', async () => {

0 commit comments

Comments
 (0)