├── .editorconfig ├── .github └── workflows │ ├── periodic.yml │ └── test.yml ├── .gitignore ├── .idea └── dictionaries │ └── fried.xml ├── .wxt ├── tsconfig.json ├── tsconfig.json.license ├── types │ ├── globals.d.ts │ ├── i18n.d.ts │ ├── imports.d.ts │ └── paths.d.ts └── wxt.d.ts ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── LICENSES └── MIT.txt ├── MAINTAINERSHIP.md ├── README.md ├── REUSE.toml ├── docs ├── screenshot.png └── screenshot.png.license ├── package.json ├── package.json.license ├── scripts └── Update-Versions.ps1 ├── src ├── assets │ ├── icon.png │ └── icon.png.license ├── entrypoints │ └── background.ts ├── icon.svg ├── icon.svg.license └── utils │ └── url-utils.ts ├── test └── url-utils.spec.ts ├── tsconfig.json ├── tsconfig.json.license ├── web-ext.config.ts ├── wxt.config.ts ├── yarn.lock └── yarn.lock.license /.editorconfig: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_size = 4 11 | 12 | [*.yml] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.github/workflows/periodic.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: Periodic Trigger 6 | on: 7 | schedule: 8 | - cron: '0 0 * * 0' # Every Sunday 9 | 10 | jobs: 11 | main: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Trigger the main workflow 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | EVENT: periodic 18 | ORG: ForNeVeR 19 | REPO: msdn-delocalizer 20 | run: | 21 | curl -d "{\"event_type\": \"${EVENT}\"}" -H "Content-Type: application/json" -H "Authorization: token ${GITHUB_TOKEN}" -H "Accept: application/vnd.github.everest-preview+json" "https://api.github.com/repos/${ORG}/${REPO}/dispatches" 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 Friedrich von Never 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: Build and Test 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | branches: 12 | - main 13 | repository_dispatch: 14 | types: [ periodic ] 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | matrix: 20 | image: [ macos-14, ubuntu-24.04, windows-2022 ] 21 | fail-fast: false 22 | runs-on: ${{ matrix.image }} 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Set up Node and Yarn cache 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: 22 29 | cache: 'yarn' 30 | - name: Install dependencies 31 | run: | 32 | yarn install 33 | - name: Build for Chrome 34 | run: | 35 | yarn run build 36 | - name: Build for Firefox 37 | run: | 38 | yarn run build:firefox 39 | - name: Run tests 40 | run: | 41 | yarn test 42 | - name: Upload the Chrome distribution 43 | if: matrix.image == 'ubuntu-24.04' 44 | uses: actions/upload-artifact@v4 45 | with: 46 | name: msdn-delocalizer.chrome 47 | path: build/chrome-mv3 48 | - name: Upload the Firefox distribution 49 | if: matrix.image == 'ubuntu-24.04' 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: msdn-delocalizer.firefox 53 | path: build/firefox-mv2 54 | 55 | licenses: 56 | runs-on: ubuntu-24.04 57 | steps: 58 | - name: Check out the sources 59 | uses: actions/checkout@v4 60 | - name: REUSE license check 61 | uses: fsfe/reuse-action@v5 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | /.idea/ 6 | /.vs/ 7 | /node_modules/ 8 | /build/ 9 | /src/public/LICENSE.md 10 | -------------------------------------------------------------------------------- /.idea/dictionaries/fried.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | delocalizer 5 | 6 | 7 | -------------------------------------------------------------------------------- /.wxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "noEmit": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "resolveJsonModule": true, 10 | "strict": true, 11 | "skipLibCheck": true, 12 | "paths": { 13 | "@": ["../src"], 14 | "@/*": ["../src/*"], 15 | "~": ["../src"], 16 | "~/*": ["../src/*"], 17 | "@@": [".."], 18 | "@@/*": ["../*"], 19 | "~~": [".."], 20 | "~~/*": ["../*"] 21 | } 22 | }, 23 | "include": [ 24 | "../**/*", 25 | "./wxt.d.ts" 26 | ], 27 | "exclude": ["../build"] 28 | } -------------------------------------------------------------------------------- /.wxt/tsconfig.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | 3 | SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /.wxt/types/globals.d.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Generated by wxt 6 | interface ImportMetaEnv { 7 | readonly MANIFEST_VERSION: 2 | 3; 8 | readonly BROWSER: string; 9 | readonly CHROME: boolean; 10 | readonly FIREFOX: boolean; 11 | readonly SAFARI: boolean; 12 | readonly EDGE: boolean; 13 | readonly OPERA: boolean; 14 | readonly COMMAND: "build" | "serve"; 15 | readonly ENTRYPOINT: string; 16 | } 17 | interface ImportMeta { 18 | readonly env: ImportMetaEnv 19 | } 20 | -------------------------------------------------------------------------------- /.wxt/types/i18n.d.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Generated by wxt 6 | import "wxt/browser"; 7 | 8 | declare module "wxt/browser" { 9 | /** 10 | * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage 11 | */ 12 | interface GetMessageOptions { 13 | /** 14 | * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage 15 | */ 16 | escapeLt?: boolean 17 | } 18 | 19 | export interface WxtI18n extends I18n.Static { 20 | /** 21 | * The extension or app ID; you might use this string to construct URLs for resources inside the extension. Even unlocalized extensions can use this message. 22 | * Note: You can't use this message in a manifest file. 23 | * 24 | * "" 25 | */ 26 | getMessage( 27 | messageName: "@@extension_id", 28 | substitutions?: string | string[], 29 | options?: GetMessageOptions, 30 | ): string; 31 | /** 32 | * "" 33 | */ 34 | getMessage( 35 | messageName: "@@ui_locale", 36 | substitutions?: string | string[], 37 | options?: GetMessageOptions, 38 | ): string; 39 | /** 40 | * The text direction for the current locale, either "ltr" for left-to-right languages such as English or "rtl" for right-to-left languages such as Japanese. 41 | * 42 | * "" 43 | */ 44 | getMessage( 45 | messageName: "@@bidi_dir", 46 | substitutions?: string | string[], 47 | options?: GetMessageOptions, 48 | ): string; 49 | /** 50 | * If the @@bidi_dir is "ltr", then this is "rtl"; otherwise, it's "ltr". 51 | * 52 | * "" 53 | */ 54 | getMessage( 55 | messageName: "@@bidi_reversed_dir", 56 | substitutions?: string | string[], 57 | options?: GetMessageOptions, 58 | ): string; 59 | /** 60 | * If the @@bidi_dir is "ltr", then this is "left"; otherwise, it's "right". 61 | * 62 | * "" 63 | */ 64 | getMessage( 65 | messageName: "@@bidi_start_edge", 66 | substitutions?: string | string[], 67 | options?: GetMessageOptions, 68 | ): string; 69 | /** 70 | * If the @@bidi_dir is "ltr", then this is "right"; otherwise, it's "left". 71 | * 72 | * "" 73 | */ 74 | getMessage( 75 | messageName: "@@bidi_end_edge", 76 | substitutions?: string | string[], 77 | options?: GetMessageOptions, 78 | ): string; 79 | getMessage( 80 | messageName: "@@extension_id" | "@@ui_locale" | "@@bidi_dir" | "@@bidi_reversed_dir" | "@@bidi_start_edge" | "@@bidi_end_edge", 81 | substitutions?: string | string[], 82 | options?: GetMessageOptions, 83 | ): string; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /.wxt/types/imports.d.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Generated by wxt 6 | export {} 7 | declare global { 8 | const ContentScriptContext: typeof import('wxt/client')['ContentScriptContext'] 9 | const InvalidMatchPattern: typeof import('wxt/sandbox')['InvalidMatchPattern'] 10 | const MatchPattern: typeof import('wxt/sandbox')['MatchPattern'] 11 | const MigrationError: typeof import('wxt/storage')['MigrationError'] 12 | const browser: typeof import('wxt/browser')['browser'] 13 | const createIframeUi: typeof import('wxt/client')['createIframeUi'] 14 | const createIntegratedUi: typeof import('wxt/client')['createIntegratedUi'] 15 | const createShadowRootUi: typeof import('wxt/client')['createShadowRootUi'] 16 | const defineAppConfig: typeof import('wxt/sandbox')['defineAppConfig'] 17 | const defineBackground: typeof import('wxt/sandbox')['defineBackground'] 18 | const defineConfig: typeof import('wxt')['defineConfig'] 19 | const defineContentScript: typeof import('wxt/sandbox')['defineContentScript'] 20 | const defineUnlistedScript: typeof import('wxt/sandbox')['defineUnlistedScript'] 21 | const defineWxtPlugin: typeof import('wxt/sandbox')['defineWxtPlugin'] 22 | const delocalizeUrl: typeof import('C:/Users/alvkn/Desktop/msdn-delocalizer/src/utils/url')['delocalizeUrl'] 23 | const fakeBrowser: typeof import('wxt/testing')['fakeBrowser'] 24 | const injectScript: typeof import('wxt/client')['injectScript'] 25 | const isMicrosoftDocumentationUrl: typeof import('C:/Users/alvkn/Desktop/msdn-delocalizer/src/utils/url')['isMicrosoftDocumentationUrl'] 26 | const storage: typeof import('wxt/storage')['storage'] 27 | const useAppConfig: typeof import('wxt/client')['useAppConfig'] 28 | } 29 | -------------------------------------------------------------------------------- /.wxt/types/paths.d.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Generated by wxt 6 | import "wxt/browser"; 7 | 8 | declare module "wxt/browser" { 9 | export type PublicPath = 10 | | "/" 11 | | "/LICENSE.md" 12 | | "/background.js" 13 | | "/icons/128.png" 14 | | "/icons/16.png" 15 | | "/icons/32.png" 16 | | "/icons/48.png" 17 | type HtmlPublicPath = Extract 18 | export interface WxtRuntime { 19 | getURL(path: PublicPath): string; 20 | getURL(path: `${HtmlPublicPath}${string}`): string; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.wxt/wxt.d.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Generated by wxt 6 | /// 7 | /// 8 | /// 9 | /// 10 | /// 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | Changelog 8 | ========= 9 | 10 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic 11 | Versioning](https://semver.org/spec/v2.0.0.html). 12 | 13 | ## [1.3.0] - 2025-03-27 14 | ### Added 15 | - Support for the new domains: 16 | - https://azure.microsoft.com/ 17 | - https://support.microsoft.com/ 18 | 19 | Thanks to @trejjam! 20 | 21 | ## [1.2.0] - 2025-02-16 22 | ### Changed 23 | - Migrate to v3 manifest for Chrome ([#34](https://github.com/ForNeVeR/msdn-delocalizer/issues/34)). Thanks to @alvkn and @KeterSCP! 24 | - Modernize the build system. Thanks to @alvkn! 25 | 26 | ## [1.1.0] - 2022-09-26 27 | ### Added 28 | - Support for a new address: learn.microsoft.com 29 | 30 | ## [1.0.0] - 2021-08-22 31 | ### Added 32 | - Extension icon 33 | 34 | ## [0.0.2] - 2021-08-22 35 | ### Added 36 | - Firefox support 37 | 38 | ### Fixed 39 | - [#9: Don't replace URL in pages that are already English](https://github.com/ForNeVeR/msdn-delocalizer/issues/9) 40 | 41 | ## [0.0.1] - 2020-04-13 42 | Initial release: a Chrome extension to delocalize Microsoft documentation sites. 43 | 44 | [0.0.1]: https://github.com/ForNeVeR/msdn-delocalizer/releases/tag/v0.0.1 45 | [0.0.2]: https://github.com/ForNeVeR/msdn-delocalizer/compare/v0.0.1...v0.0.2 46 | [1.0.0]: https://github.com/ForNeVeR/msdn-delocalizer/compare/v0.0.2...v1.0.0 47 | [1.1.0]: https://github.com/ForNeVeR/msdn-delocalizer/compare/v1.0.0...v1.1.0 48 | [1.2.0]: https://github.com/ForNeVeR/msdn-delocalizer/compare/v1.1.0...v1.2.0 49 | [1.3.0]: https://github.com/ForNeVeR/msdn-delocalizer/compare/v1.2.0...v1.3.0 50 | [Unreleased]: https://github.com/ForNeVeR/msdn-delocalizer/compare/v1.3.0...HEAD 51 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | Contributor Guide 8 | ================= 9 | 10 | License Automation 11 | ------------------ 12 | 13 | 14 | If the CI asks you to update the file licenses, follow one of these: 15 | 1. Update the headers manually (look at the existing files), something like this: 16 | ```fsharp 17 | // SPDX-FileCopyrightText: %year% %your name% <%your contact info, e.g. email%> 18 | // 19 | // SPDX-License-Identifier: MIT 20 | ``` 21 | (accommodate to the file's comment style if required). 22 | 2. Alternately, use the [REUSE][reuse] tool: 23 | ```console 24 | $ reuse annotate --license MIT --copyright '%your name% <%your contact info, e.g. email%>' %file names to annotate% 25 | ``` 26 | (Feel free to attribute the changes to "msdn-delocalizer contributors " 27 | instead of your name in a multi-author file, 28 | or if you don't want your name to be mentioned in the project's source: this doesn't mean you'll lose the copyright.) 29 | 30 | 31 | 32 | [reuse]: https://reuse.software/ 33 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 msdn-delocalizer contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 9 | following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial 12 | portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 15 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 16 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 18 | USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /MAINTAINERSHIP.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | Maintainership Instructions 8 | =========================== 9 | 10 | Releasing a New Version 11 | ----------------------- 12 | 13 | 1. Update the copyright year in [the license][license], if required. 14 | 2. Choose the new version according to [Semantic Versioning][semver]. It should consist of three numbers (i.e. `1.0.0`). 15 | 3. Run the `scripts/Update-Version.ps1` PowerShell script to update the versions. 16 | 4. Make sure there's a properly formed version entry in [the changelog][changelog]. 17 | 5. Merge the changes via a pull request. 18 | 6. Push a tag named `v` to GitHub. 19 | 7. Make a GitHub release out of tagged commit, add the changelog. 20 | 8. Get the packed extension from GitHub and attach it to the GitHub release. Release should be named "msdn-delocalizer v{version}". 21 | 9. Get the packed extension from GitHub and upload it to the [Chrome Web Store Developer portal][chrome-web-store-developer]. 22 | 10. Get the packed extension from GitHub and upload it to the [Add-ons for Firefox portal][add-ons-for-firefox]. 23 | 11. Download the tagged code snapshot from GitHub and upload it to the Add-ons for Firefox portal, when required. 24 | 25 | [add-ons-for-firefox]: https://addons.mozilla.org/en-US/developers/addon/msdn-delocalizer/edit 26 | [changelog]: ./CHANGELOG.md 27 | [chrome-web-store-developer]: https://chrome.google.com/webstore/developer/dashboard 28 | [license]: ./LICENSE.txt 29 | [semver]: https://semver.org/spec/v2.0.0.html 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | msdn-delocalizer [![Chrome Web Store][badge-chrome-web-store]][chrome-web-store] [![Firefox Add-ons][badge-firefox-add-ons]][firefox-add-ons] 8 | ================ 9 | ![Extension icon][icon] 10 | 11 | Sometimes, when looking for Microsoft documentation via a search engine, you 12 | may be presented with a link leading to a localized documentation page from 13 | Microsoft. Quality of translation varies a lot, but, as English speaker, you 14 | may choose to not deal with localization at all, and always look for English 15 | pages. 16 | 17 | msdn-delocalizer is a browser (Chrome, Firefox) extension that will automatically delocalize Microsoft documentation pages, such as MSDN, docs.microsoft.com, or learn.microsoft.com. 18 | 19 | ![Screenshot required by Chrome Web Store][screenshot] 20 | 21 | Install 22 | ------- 23 | 24 | - [Chrome Web Store][chrome-web-store] 25 | - [Firefox Add-ons][firefox-add-ons] 26 | 27 | Development 28 | ----------- 29 | ### Prerequisites 30 | 31 | - [node.js][] v22 or newer 32 | - [yarn][] 1.22 or newer 33 | - [ImageMagick][imagemagick] 7.0.11-12 or newer (only if you need to prepare a new extension icon) 34 | 35 | To set the Node-related prerequisites up, you may use [Volta][volta]: 36 | 37 | ```console 38 | $ volta install node@22 39 | $ volta install yarn@1 40 | ``` 41 | 42 | ### Build 43 | 44 | Install the dependencies: 45 | 46 | ```console 47 | $ yarn install 48 | ``` 49 | 50 | The project supports several build options. Use the following commands based on your needs: 51 | 52 | - **Development with hot reload**: 53 | Launches a development server with hot reload. The extension files are placed in the `build/{browser-name}-mv{mv-ver}` directory. 54 | ```console 55 | $ yarn run dev # for Chromium-based browsers (manifest v3) 56 | $ yarn run dev:firefox # for Firefox-based browsers (manifest v2) 57 | ``` 58 | 59 | - **Build for production**: 60 | Creates a production build of the extension in the `build/{browser-name}-mv{mv-ver}` directory. 61 | ```console 62 | $ yarn run build # for Chromium-based browsers (manifest v3) 63 | $ yarn run build:firefox # for Firefox-based browsers (manifest v2) 64 | ``` 65 | 66 | - **Create ZIP files for extension stores**: 67 | Builds the extension for both Chrome and Firefox, generating two ZIP files in the `build` directory. Firefox-specific command also creates a zip with sources as its store requires them. 68 | ```console 69 | $ yarn run zip # for Chromium-based browsers (manifest v3) 70 | $ yarn run zip:firefox # for Firefox-based browsers (manifest v2) 71 | $ yarn run zip:all # for all browsers 72 | ``` 73 | 74 | ### Test 75 | 76 | ```console 77 | $ yarn test 78 | ``` 79 | 80 | ### Icon (optional) 81 | 82 | If you have prepared a new extension icon, then run the following shell command to add it to the package: 83 | 84 | ```console 85 | $ yarn run icon 86 | ``` 87 | 88 | ### Install (developer mode) 89 | 90 | - **Chrome**: to install msdn-delocalizer to your Chrome in the [developer mode][chrome-dev-mode], load the `build/chrome-mv3` directory as the unpacked extension source, and refresh it after every rebuild. 91 | - **Firefox**: 92 | - open **Settings** via the main menu, 93 | - enter the **Extensions and Themes** page 94 | - click the **Debug Add-ons** action from the page gear menu 95 | - **Load Temporary Add-on**, navigate to the `build/firefox-mv2` and select `manifest.json` 96 | 97 | Documentation 98 | ------------- 99 | 100 | - [Changelog][changelog] 101 | - [Contributor Guide][docs.contributing] 102 | - [Maintainership][maintainership] 103 | 104 | License 105 | ------- 106 | The project is distributed under the terms of [the MIT license][docs.license]. 107 | 108 | The license indication in the project's sources is compliant with the [REUSE specification v3.3][reuse.spec]. 109 | 110 | [badge-chrome-web-store]: https://img.shields.io/chrome-web-store/v/oakieneemalliefelmegebjjagnjgpbm 111 | [badge-firefox-add-ons]: https://img.shields.io/amo/v/msdn-delocalizer 112 | [changelog]: ./CHANGELOG.md 113 | [chrome-dev-mode]: https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world#load-unpacked 114 | [chrome-web-store]: https://chrome.google.com/webstore/detail/msdn-delocalizer/oakieneemalliefelmegebjjagnjgpbm 115 | [docs.contributing]: CONTRIBUTING.md 116 | [docs.license]: ./LICENSE.txt 117 | [firefox-add-ons]: https://addons.mozilla.org/en-US/firefox/addon/msdn-delocalizer/ 118 | [icon]: ./src/icon.svg 119 | [imagemagick]: https://imagemagick.org/ 120 | [maintainership]: ./MAINTAINERSHIP.md 121 | [node.js]: https://nodejs.org/en 122 | [reuse.spec]: https://reuse.software/spec-3.3/ 123 | [screenshot]: docs/screenshot.png 124 | [volta]: https://volta.sh/ 125 | [yarn]: https://classic.yarnpkg.com/ 126 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | SPDX-PackageName = "msdn-delocalizer" 3 | SPDX-PackageSupplier = "msdn-delocalizer contributors " 4 | SPDX-PackageDownloadLocation = "https://github.com/ForNeVeR/msdn-delocalizer" 5 | 6 | [[annotations]] 7 | path = ".idea/**/**" 8 | precedence = "aggregate" 9 | SPDX-FileCopyrightText = "2025 Friedrich von Never " 10 | SPDX-License-Identifier = "MIT" 11 | -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForNeVeR/msdn-delocalizer/d2e7b143dc7f034bf6898f3a0fe0e4b8a804b23f/docs/screenshot.png -------------------------------------------------------------------------------- /docs/screenshot.png.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | 3 | SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "msdn-delocalizer", 3 | "version": "1.3.0", 4 | "description": "Browser extension to force de-localization of Microsoft documentation pages", 5 | "type": "module", 6 | "scripts": { 7 | "icon": "magick convert src/icon.svg src/assets/icon.png", 8 | "test": "vitest run", 9 | "build": "wxt build", 10 | "build:firefox": "wxt build -b firefox", 11 | "dev": "wxt", 12 | "dev:firefox": "wxt -b firefox", 13 | "zip": "wxt zip", 14 | "zip:firefox": "wxt zip -b firefox", 15 | "zip:all": "wxt zip && wxt zip -b firefox", 16 | "postinstall": "wxt prepare" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/ForNeVeR/msdn-delocalizer" 21 | }, 22 | "keywords": [ 23 | "msdn", 24 | "microsoft", 25 | "docs" 26 | ], 27 | "author": "Friedrich von Never", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/ForNeVeR/msdn-delocalizer/issues" 31 | }, 32 | "homepage": "https://github.com/ForNeVeR/msdn-delocalizer", 33 | "devDependencies": { 34 | "@wxt-dev/auto-icons": "^1.0.2", 35 | "rollup-plugin-copy": "^3.5.0", 36 | "typescript": "^5.7.3", 37 | "vitest": "^3.0.5", 38 | "wxt": "^0.19.25" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /package.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | 3 | SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /scripts/Update-Versions.ps1: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | param ( 6 | [Parameter(Mandatory)] 7 | [string] $NewVersion, 8 | 9 | $RepositoryRoot = "$PSScriptRoot/..", 10 | $PackageJson = "$RepositoryRoot/package.json", 11 | $ManifestSource = "$RepositoryRoot/wxt.config.ts" 12 | ) 13 | 14 | Set-StrictMode -Version Latest 15 | $ErrorActionPreference = 'Stop' 16 | 17 | function updateJsonFile($path, $field) { 18 | $json = Get-Content -LiteralPath $path | ConvertFrom-Json 19 | $json.$field = $NewVersion 20 | $json | ConvertTo-Json | ForEach-Object { 21 | # Fix formatting: increase indent to 4 spaces instead of default 2 22 | $_.Split("`n") | ForEach-Object { 23 | $leadingSpaces = 0 24 | while ($leadingSpaces -lt $_.Length -and $_[$leadingSpaces] -eq ' ') { 25 | ++$leadingSpaces 26 | } 27 | $_.TrimEnd().Insert(0, ' ' * $leadingSpaces) 28 | } 29 | } | Set-Content -LiteralPath $path 30 | } 31 | 32 | function updateFileWithRegEx($path, $regex) { 33 | $content = Get-Content -Raw -LiteralPath $path 34 | if (!($content -match $regex)) { 35 | throw "Cannot find regex `"$regex`" in file `"$path`"." 36 | } 37 | 38 | $replacement = $Matches[0] -replace $Matches[1], $NewVersion 39 | $newContent = $content -replace $Matches[0], $replacement 40 | Set-Content -LiteralPath $path $newContent -NoNewline 41 | } 42 | 43 | updateJsonFile $PackageJson 'version' 44 | updateFileWithRegEx $ManifestSource 'version: "([^"]*)"' 45 | -------------------------------------------------------------------------------- /src/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForNeVeR/msdn-delocalizer/d2e7b143dc7f034bf6898f3a0fe0e4b8a804b23f/src/assets/icon.png -------------------------------------------------------------------------------- /src/assets/icon.png.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | 3 | SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /src/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | import { delocalizeUrl } from "@/utils/url-utils"; 6 | import { browser } from "wxt/browser"; 7 | import { defineBackground } from "wxt/sandbox"; 8 | 9 | export default defineBackground(() => { 10 | browser.tabs.onUpdated.addListener((tabId, changeInfo) => { 11 | if (changeInfo.url == null) return; 12 | 13 | const tabUrl = new URL(changeInfo.url); 14 | const replacementUrl = delocalizeUrl(tabUrl); 15 | if (!replacementUrl || replacementUrl.toString() == tabUrl.toString()) 16 | return; 17 | 18 | browser.tabs.update(tabId, { 19 | url: replacementUrl.toString(), 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 37 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 53 | 60 | 64 | 68 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/icon.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | 3 | SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /src/utils/url-utils.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const microsoftDocumentationSites = [ 6 | "docs.microsoft.com", 7 | "msdn.microsoft.com", 8 | "learn.microsoft.com", 9 | "azure.microsoft.com", 10 | "support.microsoft.com", 11 | ]; 12 | 13 | export function isMicrosoftDocumentationUrl(url: URL): boolean { 14 | return microsoftDocumentationSites.includes(url.host); 15 | } 16 | 17 | const pathNameLanguageRegex = /^\/([a-zA-Z]{2}-[a-zA-Z]{2})\//; 18 | const englishPathName = "/en-us/"; 19 | 20 | export function delocalizeUrl(url: URL): URL | null { 21 | if (!isMicrosoftDocumentationUrl(url)) return null; 22 | 23 | const pathName = url.pathname; 24 | const newPathName = pathName.replace( 25 | pathNameLanguageRegex, 26 | englishPathName 27 | ); 28 | 29 | const result = new URL(url.toString()); 30 | result.pathname = newPathName; 31 | 32 | return result; 33 | } 34 | -------------------------------------------------------------------------------- /test/url-utils.spec.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | import { describe, it, expect } from "vitest"; 6 | import { 7 | delocalizeUrl, 8 | isMicrosoftDocumentationUrl, 9 | } from "../src/utils/url-utils"; 10 | 11 | describe("UrlUtils", () => { 12 | describe("isMicrosoftDocumentationUrl", () => { 13 | it("should return true for msdn.microsoft.com, docs.microsoft.com, learn.microsoft.com, azure.microsoft.com, and support.microsoft.com", () => { 14 | expect( 15 | isMicrosoftDocumentationUrl( 16 | new URL("https://learn.microsoft.com/abcd") 17 | ) 18 | ).toBe(true); 19 | expect( 20 | isMicrosoftDocumentationUrl( 21 | new URL("https://docs.microsoft.com/abcd") 22 | ) 23 | ).toBe(true); 24 | expect( 25 | isMicrosoftDocumentationUrl( 26 | new URL("https://msdn.microsoft.com/abcd") 27 | ) 28 | ).toBe(true); 29 | expect( 30 | isMicrosoftDocumentationUrl( 31 | new URL("https://azure.microsoft.com/abcd") 32 | ) 33 | ).toBe(true); 34 | expect( 35 | isMicrosoftDocumentationUrl( 36 | new URL("https://support.microsoft.com/abcd") 37 | ) 38 | ).toBe(true); 39 | }); 40 | 41 | it("should return false for other sites", () => { 42 | expect( 43 | isMicrosoftDocumentationUrl( 44 | new URL("https://example.microsoft.com/") 45 | ) 46 | ).toBe(false); 47 | }); 48 | }); 49 | 50 | describe("delocalizeUrl", () => { 51 | it("should return null for non-Microsoft documentation URL", () => { 52 | expect(delocalizeUrl(new URL("http://example.com"))).toBeNull(); 53 | }); 54 | 55 | it("should return the same URL for non-localized input", () => { 56 | var input = new URL("http://msdn.microsoft.com/no-localization"); 57 | expect(delocalizeUrl(input)?.toString(), input.toString()); 58 | }); 59 | 60 | it("should return the English URL for localized input", () => { 61 | var input = new URL( 62 | "https://msdn.microsoft.com/ru-ru/library/t0zfk0w1.aspx" 63 | ); 64 | expect( 65 | delocalizeUrl(input)?.toString(), 66 | "https://msdn.microsoft.com/en-us/library/t0zfk0w1.aspx" 67 | ); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | 3 | SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /web-ext.config.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | import { defineRunnerConfig } from 'wxt'; 6 | 7 | export default defineRunnerConfig({ 8 | disabled: true 9 | }); -------------------------------------------------------------------------------- /wxt.config.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | import { defineConfig } from "wxt"; 6 | import copy from 'rollup-plugin-copy'; 7 | 8 | export default defineConfig({ 9 | modules: ["@wxt-dev/auto-icons"], 10 | srcDir: "src", 11 | outDir: "build", 12 | imports: false, 13 | manifest: { 14 | name: "msdn-delocalizer", 15 | version: "1.3.0", 16 | description: 17 | "Browser extension to force de-localization of Microsoft documentation pages", 18 | permissions: ["tabs"], 19 | host_permissions: [ 20 | "https://docs.microsoft.com/*", 21 | "https://msdn.microsoft.com/*", 22 | "https://learn.microsoft.com/*", 23 | "https://azure.microsoft.com/*", 24 | "https://support.microsoft.com/*", 25 | ], 26 | background: { 27 | service_worker: "background.ts", 28 | }, 29 | }, 30 | autoIcons: { 31 | grayscaleOnDevelopment: false, 32 | }, 33 | vite: () => ({ 34 | plugins: [ 35 | copy({ 36 | targets: [{src: "LICENSE.txt", dest: "src/public" }], 37 | hook: "buildStart", 38 | }), 39 | ], 40 | }), 41 | }); 42 | -------------------------------------------------------------------------------- /yarn.lock.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 msdn-delocalizer contributors 2 | 3 | SPDX-License-Identifier: MIT 4 | --------------------------------------------------------------------------------