Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/actions/localization/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Submit English localization
description: Export and submit English strings using the shared Python localization tooling.

inputs:
app-id:
description: Python localization GitHub App ID.
required: true
private-key:
description: Python localization GitHub App private key.
required: true
source-paths:
description: JSON array of runtime source directories relative to the extension root.
default: '["src"]'
submodules:
description: Whether the extension needs recursive submodule checkout.
default: 'false'
node-version:
description: Node.js version for the localization CLI.
default: '22.21.1'
npm-registry:
description: Package feed used for the pinned localization CLI.
default: 'https://packagefeedproxy.microsoft.io/npm/'
localization-branch:
description: Existing localization-drop branch for English submissions.
default: dev

runs:
using: composite
steps:
- name: Validate localization credentials
shell: python
env:
LOCALIZATION_APP_ID: ${{ inputs.app-id }}
LOCALIZATION_PRIVATE_KEY: ${{ inputs.private-key }}
run: |
import os
if not os.environ["LOCALIZATION_APP_ID"] or not os.environ["LOCALIZATION_PRIVATE_KEY"]:
raise ValueError("Configure PYTHON_LOCALIZATION_APP_ID and PYTHON_LOCALIZATION_PRIVATE_KEY.")

- name: Checkout extension
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: ${{ inputs.submodules }}
persist-credentials: false

- name: Set up Node
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: ${{ inputs.node-version }}

- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.10'

- name: Extract English strings
shell: bash
env:
LOCALIZATION_ACTION_PATH: ${{ github.action_path }}
LOCALIZATION_SOURCE_PATHS: ${{ inputs.source-paths }}
NPM_CONFIG_REGISTRY: ${{ inputs.npm-registry }}
run: >-
python "$LOCALIZATION_ACTION_PATH/../../../scripts/localization.py"
--operation export
--extension-root "$GITHUB_WORKSPACE"
--source-paths "$LOCALIZATION_SOURCE_PATHS"
--output-directory "$RUNNER_TEMP/localization-english"

- name: Authenticate as python-localization
id: app-token
uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2
with:
app-id: ${{ inputs.app-id }}
private-key: ${{ inputs.private-key }}
owner: microsoft
repositories: vscode-python-extensions-loc
permission-contents: write

- name: Checkout localization repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: microsoft/vscode-python-extensions-loc
ref: ${{ inputs.localization-branch }}
token: ${{ steps.app-token.outputs.token }}
path: localization-translations
fetch-depth: 0

- name: Submit changed English XLF
shell: bash
env:
LOCALIZATION_ACTION_PATH: ${{ github.action_path }}
LOCALIZATION_BRANCH: ${{ inputs.localization-branch }}
run: >-
python "$LOCALIZATION_ACTION_PATH/../../../scripts/localization.py"
--operation submit
--extension-root "$GITHUB_WORKSPACE"
--output-directory "$RUNNER_TEMP/localization-english"
--drop-root "$GITHUB_WORKSPACE/localization-translations"
--branch "$LOCALIZATION_BRANCH"
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,74 @@ Use the Node.js version pinned in `.nvmrc` for development.

## Consuming in Extensions

### Shared localization automation

The composite action at `.github/actions/localization` and the standard-library
Python CLI at `scripts/localization.py` share localization automation across the
Python extensions. This tooling does not require adopting either runtime library.
Pin the action and Azure tooling checkout to the same reviewed commit.

Each extension's English-only GitHub workflow calls:

```yaml
permissions:
contents: read

jobs:
localization:
runs-on: ubuntu-latest
steps:
- uses: microsoft/vscode-common-python-lsp/.github/actions/localization@<reviewed-commit-sha>
with:
app-id: ${{ vars.PYTHON_LOCALIZATION_APP_ID }}
private-key: ${{ secrets.PYTHON_LOCALIZATION_PRIVATE_KEY }}
submodules: recursive
source-paths: '["src", "external/vscode-common-python-lsp/typescript/src"]'
```

Keep trigger paths and concurrency in the caller workflow. Omit the submodule
input and use the default `["src"]` for standalone runtime extensions. Extensions
without a `package.json` `l10n` declaration export and import manifest strings
only, even if their source contains runtime localization calls.

The action bundles checkout, Node/Python setup, extraction with pinned
`@vscode/l10n-dev@0.0.35`, app authentication, and English submission. It loads the
helper from its own action revision. Configure the app ID variable and private-key
secret in each caller (or grant organization-level access); the app needs Contents
write access only to `microsoft/vscode-python-extensions-loc`. The default npm feed
is `https://packagefeedproxy.microsoft.io/npm/`. Inputs never appear in generated
shell commands: source paths and branch values are passed through environment
variables.

English goes to `src/en/<publisher>.<name>/<publisher>.<name>-new.xlf` on the
existing `dev` branch. Unchanged XLF is a successful no-op. Submissions never
force-push; concurrent updates retry rejected pushes with a bounded rebase.

Pylance-Eng stable/pre-release templates orchestrate **pull-only** imports using
the same CLI, with source, tools, and translations in separate workspace folders:

```text
python <tools>/scripts/localization.py --operation import \
--extension-root <extension> --translations-root <drop>/out \
--output-directory <new-staging-directory>
```

The importer derives identity/runtime metadata from the extension manifest,
requires all 14 configured locales, and validates every table before copying.
Each required real-language manifest/runtime table must contain translation
evidence: a nonempty target differing from English, or a target explicitly marked
translated/final/signed-off. Individual untranslated strings are allowed;
pseudo-localization is checked for coverage but is not real-language evidence.
Missing, empty, incomplete, or wholly untranslated handbacks fail explicitly.
Do not publish while waiting for service onboarding or real translated output.

Generated root `package.nls.<locale>.json` and declared runtime locale files should
be ignored by Git but included by VSCE. Keep English `package.nls.json` tracked.
Run dependency-free regression tests with
`python -m unittest discover -s scripts -p "*_test.py"`.

### Shared runtime libraries

Consume this repository as a git submodule so the Python and TypeScript
libraries are pinned to the same commit. The VS Code Python tool extensions
place the checkout under `external/`:
Expand Down
10 changes: 10 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ def install(session: nox.Session) -> None:
@nox.session()
def tests(session: nox.Session) -> None:
"""Run Python tests."""
session.run(
"python", "-m", "unittest", "discover", "-s", "scripts", "-p", "*_test.py"
)
session.install("-e", "./python[dev]")
session.run("pytest", "python/tests", "-v")

Expand All @@ -31,3 +34,10 @@ def lint(session: nox.Session) -> None:

session.run("isort", "--check", "python/vscode_common_python_lsp")
session.run("isort", "--check", "python/tests")
session.run("flake8", "scripts/localization.py", "scripts/localization_test.py")
session.run(
"black", "--check", "scripts/localization.py", "scripts/localization_test.py"
)
session.run(
"isort", "--check", "scripts/localization.py", "scripts/localization_test.py"
)
Loading
Loading