├── .github ├── FUNDING.yml ├── dependabot.yml ├── issue_template └── workflows │ ├── build.yml │ ├── release.yml │ └── run-ui-tests.yml ├── .gitignore ├── .idea └── icon.png ├── .run ├── Run IDE for UI Tests.run.xml ├── Run IDE with Plugin.run.xml ├── Run Plugin Tests.run.xml └── Run Plugin Verification.run.xml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_CN.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── preview ├── install.png ├── preview.gif └── settings.png ├── settings.gradle.kts └── src ├── main ├── java │ ├── com │ │ └── airsaid │ │ │ └── localization │ │ │ ├── action │ │ │ └── TranslateAction.java │ │ │ ├── config │ │ │ ├── SettingsComponent.form │ │ │ ├── SettingsComponent.java │ │ │ ├── SettingsConfigurable.java │ │ │ └── SettingsState.java │ │ │ ├── constant │ │ │ └── Constants.java │ │ │ ├── services │ │ │ └── AndroidValuesService.java │ │ │ ├── task │ │ │ └── TranslateTask.java │ │ │ ├── translate │ │ │ ├── AbstractTranslator.java │ │ │ ├── TranslationException.java │ │ │ ├── TranslationResult.java │ │ │ ├── Translator.java │ │ │ ├── TranslatorConfigurable.java │ │ │ ├── impl │ │ │ │ ├── ali │ │ │ │ │ └── AliTranslator.java │ │ │ │ ├── baidu │ │ │ │ │ ├── BaiduTranslationResult.java │ │ │ │ │ └── BaiduTranslator.java │ │ │ │ ├── deepl │ │ │ │ │ ├── DeepLProTranslator.java │ │ │ │ │ ├── DeepLTranslationResult.java │ │ │ │ │ └── DeepLTranslator.java │ │ │ │ ├── google │ │ │ │ │ ├── AbsGoogleTranslator.java │ │ │ │ │ ├── GoogleToken.java │ │ │ │ │ ├── GoogleTranslationResult.java │ │ │ │ │ └── GoogleTranslator.java │ │ │ │ ├── googleapi │ │ │ │ │ ├── GoogleApiTranslationResult.java │ │ │ │ │ └── GoogleApiTranslator.java │ │ │ │ ├── microsoft │ │ │ │ │ ├── MicrosoftTranslationResult.java │ │ │ │ │ └── MicrosoftTranslator.java │ │ │ │ ├── openai │ │ │ │ │ ├── ChatGPTMessage.java │ │ │ │ │ ├── ChatGPTTranslator.java │ │ │ │ │ ├── OpenAIRequest.java │ │ │ │ │ └── OpenAIResponse.java │ │ │ │ └── youdao │ │ │ │ │ ├── YoudaoTranslationResult.java │ │ │ │ │ └── YoudaoTranslator.java │ │ │ ├── interceptors │ │ │ │ └── EscapeCharactersInterceptor.java │ │ │ ├── lang │ │ │ │ ├── Lang.java │ │ │ │ └── Languages.java │ │ │ ├── services │ │ │ │ ├── TranslationCacheService.java │ │ │ │ └── TranslatorService.java │ │ │ └── util │ │ │ │ ├── AgentUtil.java │ │ │ │ ├── GsonUtil.java │ │ │ │ ├── LRUCache.java │ │ │ │ ├── MD5.java │ │ │ │ └── UrlBuilder.java │ │ │ ├── ui │ │ │ ├── FixedLinkLabel.java │ │ │ ├── SelectLanguagesDialog.form │ │ │ ├── SelectLanguagesDialog.java │ │ │ └── SupportLanguagesDialog.java │ │ │ └── utils │ │ │ ├── LanguageUtil.java │ │ │ ├── NotificationUtil.java │ │ │ ├── SecureStorage.java │ │ │ └── TextUtil.java │ └── icons │ │ └── PluginIcons.java └── resources │ ├── META-INF │ ├── plugin.xml │ └── pluginIcon.svg │ └── icons │ ├── icon_ali.svg │ ├── icon_baidu.svg │ ├── icon_baidu_dark.svg │ ├── icon_deepl.svg │ ├── icon_google.svg │ ├── icon_microsoft.svg │ ├── icon_openai.svg │ ├── icon_translate.svg │ └── icon_youdao.svg └── test └── java └── com └── airsaid └── localization ├── translate ├── impl │ └── google │ │ └── GoogleTokenTest.java └── util │ ├── LRUCacheTest.java │ └── UrlBuilderTest.java └── utils └── TextUtilTest.java /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://afdian.net/@airsaid"] 2 | open_collective: androidlocalizeplugin 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" 16 | -------------------------------------------------------------------------------- /.github/issue_template: -------------------------------------------------------------------------------- 1 | Please ensure you have given all the following requested information in your report. 2 | 3 | 4 | #### Please select the affected platforms 5 | - [ ] Android Studio 6 | - [ ] IntelliJ IDEA 7 | 8 | #### Please select the translator to use 9 | - [ ] Google 10 | - [ ] Google (API) 11 | - [ ] Microsoft 12 | - [ ] Baidu 13 | - [ ] Youdao 14 | - [ ] Ali 15 | 16 | #### Version of Plugin and IDE 17 | - Plugin Version: 18 | - IDE Version: 19 | 20 | #### Issue details 21 | _Please provide the details of your issue_ 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions Workflow created for testing and preparing the plugin release in following steps: 2 | # - validate Gradle Wrapper, 3 | # - run 'test' and 'verifyPlugin' tasks, 4 | # - run 'buildPlugin' task and prepare artifact for the further tests, 5 | # - run 'runPluginVerifier' task, 6 | # - create a draft release. 7 | # 8 | # Workflow is triggered on push and pull_request events. 9 | # 10 | # GitHub Actions reference: https://help.github.com/en/actions 11 | # 12 | 13 | name: Build 14 | on: 15 | # Trigger the workflow on pushes to only the 'master' branch (this avoids duplicate checks being run e.g. for dependabot pull requests) 16 | push: 17 | branches: [master] 18 | # Trigger the workflow on any pull request 19 | pull_request: 20 | 21 | jobs: 22 | 23 | # Run Gradle Wrapper Validation Action to verify the wrapper's checksum 24 | # Run verifyPlugin, IntelliJ Plugin Verifier, and test Gradle tasks 25 | # Build plugin and provide the artifact for the next workflow jobs 26 | build: 27 | name: Build 28 | runs-on: ubuntu-latest 29 | outputs: 30 | version: ${{ steps.properties.outputs.version }} 31 | changelog: ${{ steps.properties.outputs.changelog }} 32 | steps: 33 | 34 | # Free GitHub Actions Environment Disk Space 35 | - name: Maximize Build Space 36 | run: | 37 | sudo rm -rf /usr/share/dotnet 38 | sudo rm -rf /usr/local/lib/android 39 | sudo rm -rf /opt/ghc 40 | 41 | # Check out current repository 42 | - name: Fetch Sources 43 | uses: actions/checkout@v3 44 | 45 | # Validate wrapper 46 | - name: Gradle Wrapper Validation 47 | uses: gradle/wrapper-validation-action@v1.1.0 48 | 49 | # Setup Java 11 environment for the next steps 50 | - name: Setup Java 51 | uses: actions/setup-java@v3 52 | with: 53 | distribution: zulu 54 | java-version: 11 55 | 56 | # Set environment variables 57 | - name: Export Properties 58 | id: properties 59 | shell: bash 60 | run: | 61 | PROPERTIES="$(./gradlew properties --console=plain -q)" 62 | VERSION="$(echo "$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')" 63 | NAME="$(echo "$PROPERTIES" | grep "^pluginName:" | cut -f2- -d ' ')" 64 | CHANGELOG="$(./gradlew getChangelog --unreleased --no-header --console=plain -q)" 65 | CHANGELOG="${CHANGELOG//'%'/'%25'}" 66 | CHANGELOG="${CHANGELOG//$'\n'/'%0A'}" 67 | CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" 68 | 69 | echo "::set-output name=version::$VERSION" 70 | echo "::set-output name=name::$NAME" 71 | echo "::set-output name=changelog::$CHANGELOG" 72 | echo "::set-output name=pluginVerifierHomeDir::~/.pluginVerifier" 73 | 74 | ./gradlew listProductsReleases # prepare list of IDEs for Plugin Verifier 75 | 76 | # Run tests 77 | - name: Run Tests 78 | run: ./gradlew test 79 | 80 | # Collect Tests Result of failed tests 81 | - name: Collect Tests Result 82 | if: ${{ failure() }} 83 | uses: actions/upload-artifact@v3 84 | with: 85 | name: tests-result 86 | path: ${{ github.workspace }}/build/reports/tests 87 | 88 | # Cache Plugin Verifier IDEs 89 | - name: Setup Plugin Verifier IDEs Cache 90 | uses: actions/cache@v3 91 | with: 92 | path: ${{ steps.properties.outputs.pluginVerifierHomeDir }}/ides 93 | key: plugin-verifier-${{ hashFiles('build/listProductsReleases.txt') }} 94 | 95 | # Run Verify Plugin task and IntelliJ Plugin Verifier tool 96 | - name: Run Plugin Verification tasks 97 | run: ./gradlew runPluginVerifier -Pplugin.verifier.home.dir=${{ steps.properties.outputs.pluginVerifierHomeDir }} 98 | 99 | # Collect Plugin Verifier Result 100 | - name: Collect Plugin Verifier Result 101 | if: ${{ always() }} 102 | uses: actions/upload-artifact@v3 103 | with: 104 | name: pluginVerifier-result 105 | path: ${{ github.workspace }}/build/reports/pluginVerifier 106 | 107 | # Prepare plugin archive content for creating artifact 108 | - name: Prepare Plugin Artifact 109 | id: artifact 110 | shell: bash 111 | run: | 112 | cd ${{ github.workspace }}/build/distributions 113 | FILENAME=`ls *.zip` 114 | unzip "$FILENAME" -d content 115 | 116 | echo "::set-output name=filename::${FILENAME:0:-4}" 117 | 118 | # Store already-built plugin as an artifact for downloading 119 | - name: Upload artifact 120 | uses: actions/upload-artifact@v3 121 | with: 122 | name: ${{ steps.artifact.outputs.filename }} 123 | path: ./build/distributions/content/*/* 124 | 125 | # Prepare a draft release for GitHub Releases page for the manual verification 126 | # If accepted and published, release workflow would be triggered 127 | releaseDraft: 128 | name: Release Draft 129 | if: github.event_name != 'pull_request' 130 | needs: build 131 | runs-on: ubuntu-latest 132 | permissions: 133 | contents: write 134 | steps: 135 | 136 | # Check out current repository 137 | - name: Fetch Sources 138 | uses: actions/checkout@v3 139 | 140 | # Remove old release drafts by using the curl request for the available releases with draft flag 141 | - name: Remove Old Release Drafts 142 | env: 143 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 144 | run: | 145 | gh api repos/{owner}/{repo}/releases \ 146 | --jq '.[] | select(.draft == true) | .id' \ 147 | | xargs -I '{}' gh api -X DELETE repos/{owner}/{repo}/releases/{} 148 | 149 | # Create new release draft - which is not publicly visible and requires manual acceptance 150 | - name: Create Release Draft 151 | env: 152 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 153 | run: | 154 | gh release create v${{ needs.build.outputs.version }} \ 155 | --draft \ 156 | --title "v${{ needs.build.outputs.version }}" \ 157 | --notes "$(cat << 'EOM' 158 | ${{ needs.build.outputs.changelog }} 159 | EOM 160 | )" 161 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions Workflow created for handling the release process based on the draft release prepared 2 | # with the Build workflow. Running the publishPlugin task requires the PUBLISH_TOKEN secret provided. 3 | 4 | name: Release 5 | on: 6 | release: 7 | types: [prereleased, released] 8 | 9 | jobs: 10 | 11 | # Prepare and publish the plugin to the Marketplace repository 12 | release: 13 | name: Publish Plugin 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: write 17 | pull-requests: write 18 | steps: 19 | 20 | # Check out current repository 21 | - name: Fetch Sources 22 | uses: actions/checkout@v3 23 | with: 24 | ref: ${{ github.event.release.tag_name }} 25 | 26 | # Setup Java 11 environment for the next steps 27 | - name: Setup Java 28 | uses: actions/setup-java@v3 29 | with: 30 | distribution: zulu 31 | java-version: 11 32 | 33 | # Set environment variables 34 | - name: Export Properties 35 | id: properties 36 | shell: bash 37 | run: | 38 | CHANGELOG="$(cat << 'EOM' | sed -e 's/^[[:space:]]*$//g' -e '/./,$!d' 39 | ${{ github.event.release.body }} 40 | EOM 41 | )" 42 | 43 | CHANGELOG="${CHANGELOG//'%'/'%25'}" 44 | CHANGELOG="${CHANGELOG//$'\n'/'%0A'}" 45 | CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" 46 | 47 | echo "::set-output name=changelog::$CHANGELOG" 48 | 49 | # Update Unreleased section with the current release note 50 | - name: Patch Changelog 51 | if: ${{ steps.properties.outputs.changelog != '' }} 52 | env: 53 | CHANGELOG: ${{ steps.properties.outputs.changelog }} 54 | run: | 55 | ./gradlew patchChangelog --release-note="$CHANGELOG" 56 | 57 | # Publish the plugin to the Marketplace 58 | - name: Publish Plugin 59 | env: 60 | PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} 61 | CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} 62 | PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} 63 | PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} 64 | run: ./gradlew publishPlugin 65 | 66 | # Upload artifact as a release asset 67 | - name: Upload Release Asset 68 | env: 69 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 70 | run: gh release upload ${{ github.event.release.tag_name }} ./build/distributions/* 71 | 72 | # Create pull request 73 | - name: Create Pull Request 74 | if: ${{ steps.properties.outputs.changelog != '' }} 75 | env: 76 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 77 | run: | 78 | VERSION="${{ github.event.release.tag_name }}" 79 | BRANCH="changelog-update-$VERSION" 80 | 81 | git config user.email "action@github.com" 82 | git config user.name "GitHub Action" 83 | 84 | git checkout -b $BRANCH 85 | git commit -am "Changelog update - $VERSION" 86 | git push --set-upstream origin $BRANCH 87 | 88 | gh pr create \ 89 | --title "Changelog update - \`$VERSION\`" \ 90 | --body "Current pull request contains patched \`CHANGELOG.md\` file for the \`$VERSION\` version." \ 91 | --base master \ 92 | --head $BRANCH 93 | -------------------------------------------------------------------------------- /.github/workflows/run-ui-tests.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions Workflow for launching UI tests on Linux, Windows, and Mac in the following steps: 2 | # - prepare and launch IDE with your plugin and robot-server plugin, which is needed to interact with UI 3 | # - wait for IDE to start 4 | # - run UI tests with separate Gradle task 5 | # 6 | # Please check https://github.com/JetBrains/intellij-ui-test-robot for information about UI tests with IntelliJ Platform 7 | # 8 | # Workflow is triggered manually. 9 | 10 | name: Run UI Tests 11 | on: 12 | workflow_dispatch 13 | 14 | jobs: 15 | 16 | testUI: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | include: 22 | - os: ubuntu-latest 23 | runIde: | 24 | export DISPLAY=:99.0 25 | Xvfb -ac :99 -screen 0 1920x1080x16 & 26 | gradle runIdeForUiTests & 27 | - os: windows-latest 28 | runIde: start gradlew.bat runIdeForUiTests 29 | - os: macos-latest 30 | runIde: ./gradlew runIdeForUiTests & 31 | 32 | steps: 33 | 34 | # Check out current repository 35 | - name: Fetch Sources 36 | uses: actions/checkout@v3 37 | 38 | # Setup Java 11 environment for the next steps 39 | - name: Setup Java 40 | uses: actions/setup-java@v3 41 | with: 42 | distribution: zulu 43 | java-version: 11 44 | 45 | # Run IDEA prepared for UI testing 46 | - name: Run IDE 47 | run: ${{ matrix.runIde }} 48 | 49 | # Wait for IDEA to be started 50 | - name: Health Check 51 | uses: jtalk/url-health-check-action@v3 52 | with: 53 | url: http://127.0.0.1:8082 54 | max-attempts: 15 55 | retry-delay: 30s 56 | 57 | # Run tests 58 | - name: Tests 59 | run: ./gradlew test 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDEA 2 | .idea 3 | 4 | # Ignore Gradle project-specific cache directory 5 | .gradle 6 | 7 | # Ignore Gradle build output directory 8 | build 9 | local.properties 10 | -------------------------------------------------------------------------------- /.idea/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Airsaid/AndroidLocalizePlugin/9cf091cbcf34091a7f6d4fec4842a27dcb2430e9/.idea/icon.png -------------------------------------------------------------------------------- /.run/Run IDE for UI Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 15 | 17 | true 18 | true 19 | false 20 | 21 | 22 | -------------------------------------------------------------------------------- /.run/Run IDE with Plugin.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /.run/Run Plugin Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.run/Run Plugin Verification.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 25 | 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Android Localize Plugin Changelog 4 | 5 | ## [Unreleased] 6 | 7 | ## [3.0.0] (2023-03-24) 8 | 9 | ### Added 10 | - Supported OpenAI ChatGPT translator. [#118](https://github.com/Airsaid/AndroidLocalizePlugin/pull/118) 11 | 12 | ### Other 13 | - Delayed error throwing to avoid losing successfully translated text. 14 | 15 | ## [2.9.0] (2022-11-29) 16 | 17 | ### Added 18 | - Supported DeepLPro translator. [#92](https://github.com/Airsaid/AndroidLocalizePlugin/issues/92) 19 | 20 | ### Fixed 21 | - Fix `xliff:g` attribute does not work. [#91](https://github.com/Airsaid/AndroidLocalizePlugin/issues/91) 22 | 23 | ## [2.8.0] (2022-10-31) 24 | 25 | ### Added 26 | - Supported DeepL translator. 27 | 28 | ## [2.7.0] (2022-10-11) 29 | 30 | ### Changed 31 | - Improve plugin description information. 32 | - Relax translation file name boundaries. 33 | 34 | ## [2.6.1] (2022-08-27) 35 | 36 | ### Added 37 | - Added rich text supported. 38 | - Added signature configuration. 39 | 40 | ### Changed 41 | - Upgrade Gradle Wrapper to `7.5.1`. 42 | - Upgrade Intellij Gradle Plugin to `1.8.1`. 43 | - Plugin description changed to be taken from the `README.md` file. 44 | 45 | ## [2.6.0] (2022-06-05) 46 | 47 | ### Added 48 | - Support Ali translator. 49 | - Support new IDE version. 50 | 51 | ### Fixed 52 | - Fix google translator translation instability. 53 | - Fix single quote character must be escaped in strings.xml. [#54](https://github.com/Airsaid/AndroidLocalizePlugin/issues/54) 54 | - Fix target folder naming. [#61](https://github.com/Airsaid/AndroidLocalizePlugin/issues/61) 55 | 56 | ## [2.5.0] (2022-02-11) 57 | 58 | ### Added 59 | - Support for preserving comments, blank lines and other characters. 60 | 61 | ## [2.4.0] (2022-01-21) 62 | 63 | ### Added 64 | - Supported custom google api key. 65 | - Supported plurals&string-array tags. 66 | - Added baidu icon of light mode. 67 | 68 | ### Changed 69 | - Changed maximum number of cacheable items to 1000. 70 | 71 | ## [2.3.0] (2021-07-09) 72 | 73 | ### Added 74 | - Add translation interval time setting. 75 | 76 | ### Changed 77 | - Replace plugin logo. 78 | 79 | ## [2.2.1] (2021-07-06) 80 | 81 | ### Fixed 82 | - Fix translation error when source text is in chinese [#33](https://github.com/Airsaid/AndroidLocalizePlugin/issues/33). 83 | 84 | ## [2.2.0] (2021-06-08) 85 | 86 | ### Added 87 | - Add power by translator description. 88 | 89 | ### Fixed 90 | - Fix incomplete Google translation long text [#31](https://github.com/Airsaid/AndroidLocalizePlugin/issues/31). 91 | 92 | ## [2.1.0] (2021-06-05) 93 | 94 | ### Added 95 | - Added Microsoft Translator. 96 | - Added "Use google.com" setting. 97 | - Supported more languages. 98 | 99 | ## [2.0.0] (2021-06-04) 100 | 101 | ### Added 102 | - Added multiple translator support. 103 | - Added "Open Translated File" option. 104 | - Added translation cache. 105 | 106 | ### Changed 107 | - Completely refactor the code. 108 | - Optimized the experience. 109 | 110 | ### Fixed 111 | - Fixed bugs. 112 | 113 | ## [1.5.0] (2020-03-28) 114 | 115 | ### Added 116 | - Added "Select All" option. 117 | 118 | ## [1.4.0] (2020-03-28) 119 | 120 | ### Added 121 | - Added proxy support. 122 | 123 | ### Fixed 124 | - Fixed bugs. 125 | 126 | ## [1.3.0] (2018-10-14) 127 | 128 | ### Added 129 | - Added "Overwrite Existing String" option. 130 | - Optimize the experience of choice. 131 | 132 | ## [1.2.0] (2018-09-28) 133 | 134 | ### Fixed 135 | - Fixed garbled bug. 136 | 137 | ## [1.1.0] (2018-09-25) 138 | 139 | ### Added 140 | - Supported for automatic detection of source file language. 141 | 142 | ## [1.0.0] (2018-09-24) 143 | - Initial release of the plugin. 144 | 145 | [Unreleased]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v3.0.0...HEAD 146 | [3.0.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v2.9.0...v3.0.0 147 | [2.9.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v2.8.0...v2.9.0 148 | [2.8.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v2.7.0...v2.8.0 149 | [2.7.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v2.6.1...v2.7.0 150 | [2.6.1]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v2.6.0...v2.6.1 151 | [2.6.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v2.5.0...v2.6.0 152 | [2.5.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v2.4.0...v2.5.0 153 | [2.4.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v2.3.0...v2.4.0 154 | [2.3.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v2.2.1...v2.3.0 155 | [2.2.1]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v2.2.0...v2.2.1 156 | [2.2.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v2.1.0...v2.2.0 157 | [2.1.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v2.0.0...v2.1.0 158 | [2.0.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v1.5...v2.0.0 159 | [1.5.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v1.4...v1.5 160 | [1.4.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v1.3...v1.4 161 | [1.3.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v1.2...v1.3 162 | [1.2.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v1.1...v1.2 163 | [1.1.0]: https://github.com/Airsaid/AndroidLocalizePlugin/compare/v1.0...v1.1 164 | [1.0.0]: https://github.com/Airsaid/AndroidLocalizePlugin/commits/v1.0 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **English** | [简体中文](README_CN.md) 2 | 3 | # ![image](https://raw.githubusercontent.com/Airsaid/AndroidLocalizePlugin/85cf5020832523ea333ad09286af55880460457a/src/main/resources/META-INF/pluginIcon.svg) AndroidLocalizePlugin 4 | [![Plugin Version](https://img.shields.io/jetbrains/plugin/v/11174)](https://plugins.jetbrains.com/plugin/11174-androidlocalize) 5 | [![Plugin Rating](https://img.shields.io/jetbrains/plugin/r/rating/11174)](https://plugins.jetbrains.com/plugin/11174-androidlocalize) 6 | [![Build](https://github.com/Airsaid/AndroidLocalizePlugin/workflows/Build/badge.svg)](https://github.com/Airsaid/AndroidLocalizePlugin/actions/workflows/build.yml) 7 | 8 | 9 | [Website](https://plugins.jetbrains.com/plugin/11174-androidlocalize) | [GitHub](https://github.com/Airsaid/AndroidLocalizePlugin) | [Issues](https://github.com/Airsaid/AndroidLocalizePlugin/issues) | [Reviews](https://plugins.jetbrains.com/plugin/11174-androidlocalize/reviews) 10 | 11 | Android localization plugin. supports multiple languages and multiple translators. 12 | 13 | # Features 14 | - Multiple translator support: 15 | - Google translator. 16 | - Microsoft translator. 17 | - Baidu translator. 18 | - Youdao translator. 19 | - Ali translator. 20 | - DeepL translator. 21 | - OpenAI ChatGPT translator. 22 | - Supports up to 100+ languages. 23 | - One key generates all translation files. 24 | - Support no translation of existing string. 25 | - Support for specifying that text is not translated. 26 | - Support for caching translated strings. 27 | - Support to set the translation interval time. 28 | 29 | # Usage 30 | - Step 1: Select the `values/strings.xml`(or any string resource in `values` directory). 31 | - Step 2: Right click and select "Translate to Other Languages". 32 | - Step 3: Select the languages to be translated. 33 | - Step 4: Click OK. 34 | 35 | 36 | 37 | # Preview 38 | ![image](preview/preview.gif) 39 | ![image](preview/settings.png) 40 | 41 | # Install 42 | [![Install Plugin](preview/install.png)](https://plugins.jetbrains.com/plugin/11174-androidlocalize) 43 | 44 | # FAQ 45 | - Q: How to ignore translation? 46 | 47 | A: Use the [translatable or xliff:g](https://developer.android.com/guide/topics/resources/localization#managing-strings) tags. for example: 48 | ``` 49 | HelloAndroid 50 | Check out our 5\u2605 51 | Visit us at https://github.com/Airsaid/AndroidLocalizePlugin 52 | Learn more at Muggle Game Studio 53 | ``` 54 | **Note: Display one line without extra line breaks and spaces in between.** 55 | - Q: Translation failure: java.net.HttpRetryException: cannot retry due to redirection, in streaming mode 56 | 57 | A: If you are using the default translation engine (Google), then you can try switching to another engine on the settings page and use your own account for translation. Because the default translation engine is not stable. 58 | 59 | # ChangeLog 60 | [ChangeLog](CHANGELOG.md) 61 | 62 | # Support and Donations 63 | 64 | You can contribute and support this project by doing any of the following: 65 | 66 | - Star the project on GitHub. 67 | - Give feedback. 68 | - Commit PR. 69 | - Contribute your ideas/suggestions. 70 | - Share the plugin with your friends/colleagues. 71 | - If you like the plugin, please consider making a donation to keep the plugin active: 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 87 | 92 | 97 | 98 |
Open CollectiveWeChat PayAlipay
83 | 84 | Donate To Our Collective 85 | 86 | 88 | 89 | WeChat Play 90 | 91 | 93 | 94 | Alipay 95 | 96 |
99 | 100 | **Thank you for your support!** 101 | 102 | # Sponsors 103 | [![Development powered by JetBrains](https://pic.stackoverflow.wiki/uploadImages/111/201/226/60/2021/06/20/18/45/3aba65f5-1231-4c9a-817f-83cd5a29fd0c.svg)](https://jb.gg/OpenSourc) 104 | 105 | # License 106 | ``` 107 | Copyright 2018 Airsaid. https://github.com/airsaid 108 | 109 | Licensed under the Apache License, Version 2.0 (the "License"); 110 | you may not use this file except in compliance with the License. 111 | You may obtain a copy of the License at 112 | 113 | http://www.apache.org/licenses/LICENSE-2.0 114 | 115 | Unless required by applicable law or agreed to in writing, software 116 | distributed under the License is distributed on an "AS IS" BASIS, 117 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 118 | See the License for the specific language governing permissions and 119 | limitations under the License. 120 | ``` 121 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | [English](README.md) | **简体中文** 2 | 3 | # ![image](https://raw.githubusercontent.com/Airsaid/AndroidLocalizePlugin/85cf5020832523ea333ad09286af55880460457a/src/main/resources/META-INF/pluginIcon.svg) AndroidLocalizePlugin 4 | [![Plugin Version](https://img.shields.io/jetbrains/plugin/v/11174)](https://plugins.jetbrains.com/plugin/11174-androidlocalize) 5 | [![Plugin Rating](https://img.shields.io/jetbrains/plugin/r/rating/11174)](https://plugins.jetbrains.com/plugin/11174-androidlocalize) 6 | [![Build](https://github.com/Airsaid/AndroidLocalizePlugin/workflows/Build/badge.svg)](https://github.com/Airsaid/AndroidLocalizePlugin/actions/workflows/build.yml) 7 | 8 | [Website](https://plugins.jetbrains.com/plugin/11174-androidlocalize) | [GitHub](https://github.com/Airsaid/AndroidLocalizePlugin) | [Issues](https://github.com/Airsaid/AndroidLocalizePlugin/issues) | [Reviews](https://plugins.jetbrains.com/plugin/11174-androidlocalize/reviews) 9 | 10 | Android 本地化插件,支持多种语言和翻译器。 11 | 12 | # 功能 13 | - 多翻译器支持: 14 | - Google 翻译。 15 | - 微软翻译。 16 | - 百度翻译。 17 | - 有道翻译。 18 | - 阿里翻译。 19 | - DeepL 翻译。 20 | - OpenAI ChatGPT 翻译。 21 | - 支持最多 100+ 语言。 22 | - 一键生成所有翻译文件。 23 | - 支持不翻译已经存在的 string。 24 | - 支持不翻译指定的文本。 25 | - 支持缓存已翻译的 strings。 26 | - 支持设置翻译间隔时间。 27 | 28 | # 使用 29 | - 第一步:选择 `values/strings.xml` 文件(或者是 values 目录下的任何资源文件)。 30 | - 第二步:右键选择:“Translate to Other Languages”。 31 | - 第三步:勾选上需要翻译的语言。 32 | - 第四步:点击 OK。 33 | 34 | # 预览 35 | ![image](preview/preview.gif) 36 | ![image](preview/settings.png) 37 | 38 | # 安装 39 | [![Install Plugin](preview/install.png)](https://plugins.jetbrains.com/plugin/11174-androidlocalize) 40 | 41 | # 常见问题 42 | - 问题:如何忽略不让其翻译? 43 | 44 | 回答:可以使用 [translatable 或 xliff:g](https://developer.android.com/guide/topics/resources/localization#managing-strings) 标签。示例: 45 | ``` 46 | HelloAndroid 47 | Check out our 5\u2605 48 | Visit us at https://github.com/Airsaid/AndroidLocalizePlugin 49 | Learn more at Muggle Game Studio 50 | ``` 51 | **注意:一行展示,中间不要有多余的换行和空格。** 52 | 53 | - 问题:Translation failure: java.net.HttpRetryException: cannot retry due to redirection, in streaming mode 54 | 55 | 回答:如果你使用的是默认的翻译引擎(Google),那么你可以在设置页面尝试切换到其他引擎,并使用自己的账号进行翻译。因为默认的翻译引擎并不稳定。 56 | 57 | # 更新日志 58 | [更新日志](CHANGELOG.md) 59 | 60 | # 支持和捐赠 61 | 62 | 您可以通过执行以下任意操作来贡献和支持此项目: 63 | 64 | - 在 GitHub 上 Star 该项目。 65 | - 反馈问题。 66 | - 提交 PR。 67 | - 提出您的想法或建议。 68 | - 将插件分享给您的朋友和同事。 69 | - 如果您喜欢这个插件,请考虑捐赠以维持该插件和后续的更新: 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 85 | 90 | 95 | 96 |
Open Collective微信支付支付宝
81 | 82 | Donate To Our Collective 83 | 84 | 86 | 87 | WeChat Play 88 | 89 | 91 | 92 | Alipay 93 | 94 |
97 | 98 | **感谢您的支持!** 99 | 100 | # 赞助商 101 | [![Development powered by JetBrains](https://pic.stackoverflow.wiki/uploadImages/111/201/226/60/2021/06/20/18/45/3aba65f5-1231-4c9a-817f-83cd5a29fd0c.svg)](https://jb.gg/OpenSourc) 102 | 103 | # 许可证 104 | ``` 105 | Copyright 2018 Airsaid. https://github.com/airsaid 106 | 107 | Licensed under the Apache License, Version 2.0 (the "License"); 108 | you may not use this file except in compliance with the License. 109 | You may obtain a copy of the License at 110 | 111 | http://www.apache.org/licenses/LICENSE-2.0 112 | 113 | Unless required by applicable law or agreed to in writing, software 114 | distributed under the License is distributed on an "AS IS" BASIS, 115 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 116 | See the License for the specific language governing permissions and 117 | limitations under the License. 118 | ``` 119 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.changelog.Changelog 2 | import org.jetbrains.changelog.date 3 | import org.jetbrains.changelog.markdownToHTML 4 | 5 | fun properties(key: String) = project.findProperty(key).toString() 6 | 7 | plugins { 8 | // Java support 9 | id("java") 10 | // Gradle IntelliJ Plugin 11 | id("org.jetbrains.intellij") version "1.13.3" 12 | // Gradle Changelog Plugin 13 | id("org.jetbrains.changelog") version "2.1.2" 14 | } 15 | 16 | group = properties("pluginGroup") 17 | version = properties("pluginVersion") 18 | 19 | // Configure project's dependencies 20 | repositories { 21 | mavenCentral() 22 | } 23 | 24 | // Configure Gradle IntelliJ Plugin - read more: https://github.com/JetBrains/gradle-intellij-plugin 25 | intellij { 26 | pluginName.set(properties("pluginName")) 27 | version.set(properties("platformVersion")) 28 | type.set(properties("platformType")) 29 | 30 | // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file. 31 | plugins.set(properties("platformPlugins").split(',').map(String::trim).filter(String::isNotEmpty)) 32 | } 33 | 34 | // Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin 35 | changelog { 36 | groups.empty() 37 | header.set(provider { "${version.get()} (${date()})" }) 38 | repositoryUrl.set(properties("pluginRepositoryUrl")) 39 | } 40 | 41 | tasks { 42 | // Set the JVM compatibility versions 43 | properties("javaVersion").let { 44 | withType { 45 | sourceCompatibility = it 46 | targetCompatibility = it 47 | options.encoding = "UTF-8" 48 | } 49 | } 50 | 51 | wrapper { 52 | gradleVersion = properties("gradleVersion") 53 | } 54 | 55 | patchPluginXml { 56 | version.set(properties("pluginVersion")) 57 | sinceBuild.set(properties("pluginSinceBuild")) 58 | untilBuild.set(properties("pluginUntilBuild")) 59 | 60 | // Extract the section from README.md and provide for the plugin's manifest 61 | pluginDescription.set( 62 | projectDir.resolve("README.md").readText().lines().run { 63 | val start = "" 64 | val end = "" 65 | 66 | if (!containsAll(listOf(start, end))) { 67 | throw GradleException("Plugin description section not found in README.md:\n$start ... $end") 68 | } 69 | subList(indexOf(start) + 1, indexOf(end)) 70 | }.joinToString("\n").run { markdownToHTML(this) } 71 | ) 72 | 73 | // Get the latest available change notes from the changelog file 74 | changeNotes.set(provider { 75 | with(changelog) { 76 | renderItem( 77 | getOrNull(properties("pluginVersion")) ?: getLatest(), 78 | Changelog.OutputType.HTML, 79 | ) 80 | } 81 | }) 82 | } 83 | 84 | test { 85 | useJUnitPlatform() 86 | } 87 | 88 | // Configure UI tests plugin 89 | // Read more: https://github.com/JetBrains/intellij-ui-test-robot 90 | runIdeForUiTests { 91 | systemProperty("robot-server.port", "8082") 92 | systemProperty("ide.mac.message.dialogs.as.sheets", "false") 93 | systemProperty("jb.privacy.policy.text", "") 94 | systemProperty("jb.consents.confirmation.enabled", "false") 95 | } 96 | 97 | signPlugin { 98 | certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) 99 | privateKey.set(System.getenv("PRIVATE_KEY")) 100 | password.set(System.getenv("PRIVATE_KEY_PASSWORD")) 101 | } 102 | 103 | publishPlugin { 104 | dependsOn("patchChangelog") 105 | token.set(System.getenv("PUBLISH_TOKEN")) 106 | // pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3 107 | // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more: 108 | // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel 109 | channels.set(listOf(properties("pluginVersion").split('-').getOrElse(1) { "default" }.split('.').first())) 110 | } 111 | } 112 | 113 | dependencies { 114 | // https://github.com/google/auto/tree/master/service 115 | compileOnly("com.google.auto.service:auto-service-annotations:1.1.1") 116 | annotationProcessor("com.google.auto.service:auto-service:1.1.1") 117 | 118 | implementation("com.google.code.gson:gson:2.10.1") 119 | implementation("com.aliyun:alimt20181012:1.0.3") 120 | 121 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0") 122 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.3") 123 | 124 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # IntelliJ Platform Artifacts Repositories 2 | # -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html 3 | 4 | pluginGroup = com.airsaid 5 | pluginName = AndroidLocalize 6 | pluginRepositoryUrl = https://github.com/Airsaid/AndroidLocalizePlugin 7 | # SemVer format -> https://semver.org 8 | pluginVersion = 3.0.0 9 | 10 | # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html 11 | # for insight into build numbers and IntelliJ Platform versions. 12 | pluginSinceBuild = 203 13 | pluginUntilBuild = 14 | 15 | # IntelliJ Platform Properties -> https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties 16 | platformType = IC 17 | platformVersion = 2020.3.4 18 | 19 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html 20 | # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 21 | platformPlugins = com.intellij.java 22 | 23 | # Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3 24 | javaVersion = 11 25 | 26 | # Gradle Releases -> https://github.com/gradle/gradle/releases 27 | gradleVersion = 7.5.1 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Airsaid/AndroidLocalizePlugin/9cf091cbcf34091a7f6d4fec4842a27dcb2430e9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /preview/install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Airsaid/AndroidLocalizePlugin/9cf091cbcf34091a7f6d4fec4842a27dcb2430e9/preview/install.png -------------------------------------------------------------------------------- /preview/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Airsaid/AndroidLocalizePlugin/9cf091cbcf34091a7f6d4fec4842a27dcb2430e9/preview/preview.gif -------------------------------------------------------------------------------- /preview/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Airsaid/AndroidLocalizePlugin/9cf091cbcf34091a7f6d4fec4842a27dcb2430e9/preview/settings.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "AndroidLocalizePlugin" -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/action/TranslateAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.action; 19 | 20 | import com.airsaid.localization.config.SettingsState; 21 | import com.airsaid.localization.services.AndroidValuesService; 22 | import com.airsaid.localization.task.TranslateTask; 23 | import com.airsaid.localization.translate.lang.Lang; 24 | import com.airsaid.localization.ui.SelectLanguagesDialog; 25 | import com.airsaid.localization.utils.NotificationUtil; 26 | import com.intellij.openapi.actionSystem.AnAction; 27 | import com.intellij.openapi.actionSystem.AnActionEvent; 28 | import com.intellij.openapi.actionSystem.CommonDataKeys; 29 | import com.intellij.openapi.project.Project; 30 | import com.intellij.psi.PsiElement; 31 | import com.intellij.psi.PsiFile; 32 | import com.intellij.psi.xml.XmlTag; 33 | import org.jetbrains.annotations.NotNull; 34 | 35 | import java.util.List; 36 | 37 | /** 38 | * Translate android string value to other languages that can be used to localize your Android APP. 39 | * 40 | * @author airsaid 41 | */ 42 | public class TranslateAction extends AnAction implements SelectLanguagesDialog.OnClickListener { 43 | 44 | private Project mProject; 45 | private PsiFile mValueFile; 46 | private List mValues; 47 | private final AndroidValuesService mValueService = AndroidValuesService.getInstance(); 48 | 49 | @Override 50 | public void actionPerformed(AnActionEvent e) { 51 | mProject = e.getRequiredData(CommonDataKeys.PROJECT); 52 | mValueFile = e.getRequiredData(CommonDataKeys.PSI_FILE); 53 | 54 | SettingsState.getInstance().initSetting(); 55 | 56 | mValueService.loadValuesByAsync(mValueFile, values -> { 57 | if (!isTranslatable(values)) { 58 | NotificationUtil.notifyInfo(mProject, "The " + mValueFile.getName() + " has no text to translate."); 59 | return; 60 | } 61 | mValues = values; 62 | showSelectLanguageDialog(); 63 | }); 64 | } 65 | 66 | // Verify that there is a text in the value file that needs to be translated. 67 | private boolean isTranslatable(@NotNull List values) { 68 | for (PsiElement psiElement : values) { 69 | if (psiElement instanceof XmlTag) { 70 | if (mValueService.isTranslatable((XmlTag) psiElement)) { 71 | return true; 72 | } 73 | } 74 | } 75 | return false; 76 | } 77 | 78 | private void showSelectLanguageDialog() { 79 | SelectLanguagesDialog dialog = new SelectLanguagesDialog(mProject); 80 | dialog.setOnClickListener(this); 81 | dialog.show(); 82 | } 83 | 84 | @Override 85 | public void update(@NotNull AnActionEvent e) { 86 | // The translation option is only show when xml file from values is selected 87 | Project project = e.getData(CommonDataKeys.PROJECT); 88 | boolean isSelectValueFile = mValueService.isValueFile(e.getData(CommonDataKeys.PSI_FILE)); 89 | e.getPresentation().setEnabledAndVisible(project != null && isSelectValueFile); 90 | } 91 | 92 | @Override 93 | public void onClickListener(List selectedLanguage) { 94 | TranslateTask translationTask = new TranslateTask(mProject, "Translating...", selectedLanguage, mValues, mValueFile); 95 | translationTask.setOnTranslateListener(new TranslateTask.OnTranslateListener() { 96 | @Override 97 | public void onTranslateSuccess() { 98 | NotificationUtil.notifyInfo(mProject, "Translation completed!"); 99 | } 100 | 101 | @Override 102 | public void onTranslateError(Throwable e) { 103 | NotificationUtil.notifyError(mProject, "Translation failure: " + e.getLocalizedMessage()); 104 | } 105 | }); 106 | translationTask.queue(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/config/SettingsConfigurable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.config; 19 | 20 | import com.airsaid.localization.constant.Constants; 21 | import com.airsaid.localization.services.AndroidValuesService; 22 | import com.airsaid.localization.translate.AbstractTranslator; 23 | import com.airsaid.localization.translate.services.TranslatorService; 24 | import com.intellij.openapi.diagnostic.Logger; 25 | import com.intellij.openapi.options.Configurable; 26 | import com.intellij.openapi.options.ConfigurationException; 27 | import com.intellij.openapi.util.text.StringUtil; 28 | import org.jetbrains.annotations.Nullable; 29 | 30 | import javax.swing.*; 31 | import java.util.Map; 32 | 33 | /** 34 | * @author airsaid 35 | */ 36 | public class SettingsConfigurable implements Configurable { 37 | 38 | private static final Logger LOG = Logger.getInstance(SettingsConfigurable.class); 39 | 40 | private SettingsComponent settingsComponent; 41 | 42 | @Override 43 | public String getDisplayName() { 44 | return Constants.PLUGIN_NAME; 45 | } 46 | 47 | @Override 48 | public JComponent getPreferredFocusedComponent() { 49 | return settingsComponent.getPreferredFocusedComponent(); 50 | } 51 | 52 | @Override 53 | public @Nullable JComponent createComponent() { 54 | settingsComponent = new SettingsComponent(); 55 | initComponents(); 56 | return settingsComponent.getContent(); 57 | } 58 | 59 | private void initComponents() { 60 | SettingsState settingsState = SettingsState.getInstance(); 61 | Map translators = TranslatorService.getInstance().getTranslators(); 62 | settingsComponent.setTranslators(translators); 63 | settingsComponent.setSelectedTranslator(translators.get(settingsState.getSelectedTranslator().getKey())); 64 | settingsComponent.setEnableCache(settingsState.isEnableCache()); 65 | settingsComponent.setMaxCacheSize(settingsState.getMaxCacheSize()); 66 | settingsComponent.setTranslationInterval(settingsState.getTranslationInterval()); 67 | settingsComponent.setSkipNonTranslatable(settingsState.isSkipNonTranslatable()); 68 | } 69 | 70 | @Override 71 | public boolean isModified() { 72 | SettingsState settingsState = SettingsState.getInstance(); 73 | AbstractTranslator selectedTranslator = settingsComponent.getSelectedTranslator(); 74 | boolean isChanged = settingsState.getSelectedTranslator() == selectedTranslator; 75 | isChanged |= settingsState.getAppId(selectedTranslator.getKey()).equals(selectedTranslator.getAppId()); 76 | isChanged |= settingsState.getAppKey(selectedTranslator.getKey()).equals(selectedTranslator.getAppKey()); 77 | isChanged |= settingsState.isEnableCache() == settingsComponent.isEnableCache(); 78 | isChanged |= settingsState.getMaxCacheSize() == settingsComponent.getMaxCacheSize(); 79 | isChanged |= settingsState.getTranslationInterval() == settingsComponent.getTranslationInterval(); 80 | isChanged |= settingsState.isSkipNonTranslatable() == settingsComponent.isSkipNonTranslatable(); 81 | LOG.info("isModified: " + isChanged); 82 | return isChanged; 83 | } 84 | 85 | @Override 86 | public void apply() throws ConfigurationException { 87 | SettingsState settingsState = SettingsState.getInstance(); 88 | AbstractTranslator selectedTranslator = settingsComponent.getSelectedTranslator(); 89 | LOG.info("apply selectedTranslator: " + selectedTranslator.getName()); 90 | 91 | // Verify that the required parameters are not configured 92 | if (selectedTranslator.isNeedAppId() && StringUtil.isEmpty(settingsComponent.getAppId())) { 93 | throw new ConfigurationException(selectedTranslator.getAppIdDisplay() + " not configured"); 94 | } 95 | if (selectedTranslator.isNeedAppKey() && StringUtil.isEmpty(settingsComponent.getAppKey())) { 96 | throw new ConfigurationException(selectedTranslator.getAppKeyDisplay() + " not configured"); 97 | } 98 | 99 | settingsState.setSelectedTranslator(selectedTranslator); 100 | if (selectedTranslator.isNeedAppId()) { 101 | settingsState.setAppId(selectedTranslator.getKey(), settingsComponent.getAppId()); 102 | } 103 | if (selectedTranslator.isNeedAppKey()) { 104 | settingsState.setAppKey(selectedTranslator.getKey(), settingsComponent.getAppKey()); 105 | } 106 | settingsState.setEnableCache(settingsComponent.isEnableCache()); 107 | settingsState.setMaxCacheSize(settingsComponent.getMaxCacheSize()); 108 | settingsState.setTranslationInterval(settingsComponent.getTranslationInterval()); 109 | settingsState.setSkipNonTranslatable(settingsComponent.isSkipNonTranslatable()); 110 | 111 | TranslatorService translatorService = TranslatorService.getInstance(); 112 | translatorService.setSelectedTranslator(selectedTranslator); 113 | translatorService.setEnableCache(settingsComponent.isEnableCache()); 114 | translatorService.setMaxCacheSize(settingsComponent.getMaxCacheSize()); 115 | translatorService.setTranslationInterval(settingsComponent.getTranslationInterval()); 116 | 117 | AndroidValuesService.getInstance().setSkipNonTranslatable(settingsComponent.isSkipNonTranslatable()); 118 | } 119 | 120 | @Override 121 | public void reset() { 122 | LOG.info("reset"); 123 | SettingsState settingsState = SettingsState.getInstance(); 124 | AbstractTranslator selectedTranslator = settingsState.getSelectedTranslator(); 125 | settingsComponent.setSelectedTranslator(selectedTranslator); 126 | settingsComponent.setAppId(settingsState.getAppId(selectedTranslator.getKey())); 127 | settingsComponent.setAppKey(settingsState.getAppKey(selectedTranslator.getKey())); 128 | settingsComponent.setEnableCache(settingsState.isEnableCache()); 129 | settingsComponent.setMaxCacheSize(settingsState.getMaxCacheSize()); 130 | settingsComponent.setTranslationInterval(settingsState.getTranslationInterval()); 131 | settingsComponent.setSkipNonTranslatable(settingsState.isSkipNonTranslatable()); 132 | } 133 | 134 | @Override 135 | public void disposeUIResources() { 136 | settingsComponent = null; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/config/SettingsState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.config; 19 | 20 | import com.airsaid.localization.services.AndroidValuesService; 21 | import com.airsaid.localization.translate.AbstractTranslator; 22 | import com.airsaid.localization.translate.services.TranslatorService; 23 | import com.airsaid.localization.utils.SecureStorage; 24 | import com.intellij.openapi.components.*; 25 | import com.intellij.openapi.diagnostic.Logger; 26 | import com.intellij.openapi.util.text.StringUtil; 27 | import org.jetbrains.annotations.NotNull; 28 | import org.jetbrains.annotations.Nullable; 29 | 30 | import java.util.Collection; 31 | import java.util.HashMap; 32 | import java.util.Map; 33 | 34 | /** 35 | * @author airsaid 36 | */ 37 | @State( 38 | name = "com.airsaid.localization.config.SettingsState", 39 | storages = {@Storage("androidLocalizeSettings.xml")} 40 | ) 41 | @Service 42 | public final class SettingsState implements PersistentStateComponent { 43 | 44 | private static final Logger LOG = Logger.getInstance(SettingsState.class); 45 | 46 | private final Map appKeyStorage; 47 | 48 | private State state = new State(); 49 | 50 | public SettingsState() { 51 | appKeyStorage = new HashMap<>(); 52 | TranslatorService translatorService = TranslatorService.getInstance(); 53 | Collection translators = translatorService.getTranslators().values(); 54 | for (AbstractTranslator translator : translators) { 55 | if (translatorService.getDefaultTranslator() != translator) { 56 | appKeyStorage.put(translator.getKey(), new SecureStorage(translator.getKey())); 57 | } 58 | } 59 | } 60 | 61 | public static SettingsState getInstance() { 62 | return ServiceManager.getService(SettingsState.class); 63 | } 64 | 65 | public void initSetting() { 66 | TranslatorService translatorService = TranslatorService.getInstance(); 67 | AbstractTranslator selectedTranslator = translatorService.getSelectedTranslator(); 68 | if (selectedTranslator == null) { 69 | LOG.info("initSetting"); 70 | translatorService.setSelectedTranslator(getSelectedTranslator()); 71 | translatorService.setEnableCache(isEnableCache()); 72 | translatorService.setMaxCacheSize(getMaxCacheSize()); 73 | translatorService.setTranslationInterval(getTranslationInterval()); 74 | } 75 | 76 | AndroidValuesService.getInstance().setSkipNonTranslatable(isSkipNonTranslatable()); 77 | } 78 | 79 | public AbstractTranslator getSelectedTranslator() { 80 | return StringUtil.isEmpty(state.selectedTranslatorKey) ? TranslatorService.getInstance().getDefaultTranslator() : 81 | TranslatorService.getInstance().getTranslators().get(state.selectedTranslatorKey); 82 | } 83 | 84 | public void setSelectedTranslator(AbstractTranslator translator) { 85 | this.state.selectedTranslatorKey = translator.getKey(); 86 | } 87 | 88 | public void setAppId(@NotNull String translatorKey, @NotNull String appId) { 89 | state.appIds.put(translatorKey, appId); 90 | } 91 | 92 | @NotNull 93 | public String getAppId(String translatorKey) { 94 | String appId = state.appIds.get(translatorKey); 95 | return appId != null ? appId : ""; 96 | } 97 | 98 | public void setAppKey(@NotNull String translatorKey, @NotNull String appKey) { 99 | SecureStorage secureStorage = appKeyStorage.get(translatorKey); 100 | if (secureStorage != null) { 101 | secureStorage.save(appKey); 102 | } 103 | } 104 | 105 | @NotNull 106 | public String getAppKey(@NotNull String translatorKey) { 107 | SecureStorage secureStorage = appKeyStorage.get(translatorKey); 108 | return secureStorage != null ? secureStorage.read() : ""; 109 | } 110 | 111 | public boolean isEnableCache() { 112 | return state.isEnableCache; 113 | } 114 | 115 | public void setEnableCache(boolean isEnable) { 116 | state.isEnableCache = isEnable; 117 | } 118 | 119 | public int getMaxCacheSize() { 120 | return state.maxCacheSize; 121 | } 122 | 123 | public void setMaxCacheSize(int maxCacheSize) { 124 | state.maxCacheSize = maxCacheSize; 125 | } 126 | 127 | public int getTranslationInterval() { 128 | return state.translationInterval; 129 | } 130 | 131 | public void setTranslationInterval(int intervalTime) { 132 | state.translationInterval = intervalTime; 133 | } 134 | 135 | public boolean isSkipNonTranslatable() { 136 | return state.isSkipNonTranslatable; 137 | } 138 | 139 | public void setSkipNonTranslatable(boolean isSkipNonTranslatable) { 140 | state.isSkipNonTranslatable = isSkipNonTranslatable; 141 | } 142 | 143 | @Override 144 | public @Nullable SettingsState.State getState() { 145 | return state; 146 | } 147 | 148 | @Override 149 | public void loadState(@NotNull State state) { 150 | this.state = state; 151 | } 152 | 153 | static class State { 154 | public String selectedTranslatorKey; 155 | public Map appIds = new HashMap<>(); 156 | public boolean isEnableCache = true; 157 | public int maxCacheSize = 500; 158 | public int translationInterval = 2; // 2 second 159 | public boolean isSkipNonTranslatable; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/constant/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.constant; 19 | 20 | /** 21 | * Constant Store. 22 | * 23 | * @author airsaid 24 | */ 25 | public interface Constants { 26 | 27 | String PLUGIN_NAME = "AndroidLocalize"; 28 | 29 | String PLUGIN_ID = "com.github.airsaid.androidlocalize"; 30 | 31 | String KEY_SELECTED_LANGUAGES = PLUGIN_ID.concat(".selected_languages"); 32 | 33 | String KEY_IS_OVERWRITE_EXISTING_STRING = PLUGIN_ID.concat(".is_overwrite_existing_string"); 34 | 35 | String KEY_IS_SELECT_ALL = PLUGIN_ID.concat(".is_select_all"); 36 | 37 | String KEY_IS_OPEN_TRANSLATED_FILE = PLUGIN_ID.concat(".is_open_translated_file"); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/AbstractTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate; 19 | 20 | import com.airsaid.localization.config.SettingsState; 21 | import com.airsaid.localization.translate.lang.Lang; 22 | import com.intellij.openapi.diagnostic.Logger; 23 | import com.intellij.openapi.util.Pair; 24 | import com.intellij.util.io.HttpRequests; 25 | import com.intellij.util.io.RequestBuilder; 26 | import org.jetbrains.annotations.NotNull; 27 | import org.jetbrains.annotations.Nullable; 28 | 29 | import javax.swing.*; 30 | import java.net.URLEncoder; 31 | import java.nio.charset.StandardCharsets; 32 | import java.util.List; 33 | import java.util.stream.Collectors; 34 | 35 | /** 36 | * @author airsaid 37 | */ 38 | public abstract class AbstractTranslator implements Translator, TranslatorConfigurable { 39 | 40 | protected static final Logger LOG = Logger.getInstance(AbstractTranslator.class); 41 | 42 | private static final String CONTENT_TYPE = "application/x-www-form-urlencoded"; 43 | 44 | @Override 45 | public String doTranslate(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) throws TranslationException { 46 | checkSupportedLanguages(fromLang, toLang, text); 47 | 48 | String requestUrl = getRequestUrl(fromLang, toLang, text); 49 | RequestBuilder requestBuilder = HttpRequests.post(requestUrl, CONTENT_TYPE); 50 | // Set the timeout time to 60 seconds. 51 | requestBuilder.connectTimeout(60 * 1000); 52 | configureRequestBuilder(requestBuilder); 53 | 54 | try { 55 | return requestBuilder.connect(request -> { 56 | String requestParams = getRequestParams(fromLang, toLang, text) 57 | .stream() 58 | .map(pair -> { 59 | return pair.first.concat("=").concat(URLEncoder.encode(pair.second, StandardCharsets.UTF_8)); 60 | }) 61 | .collect(Collectors.joining("&")); 62 | if (!requestParams.isEmpty()) { 63 | request.write(requestParams); 64 | } 65 | String requestBody = getRequestBody(fromLang, toLang, text); 66 | if (!requestBody.isEmpty()) { 67 | request.write(requestBody); 68 | } 69 | 70 | String resultText = request.readString(); 71 | return parsingResult(fromLang, toLang, text, resultText); 72 | }); 73 | } catch (Exception e) { 74 | e.printStackTrace(); 75 | LOG.error(e.getMessage(), e); 76 | throw new TranslationException(fromLang, toLang, text, e); 77 | } 78 | } 79 | 80 | @Override 81 | public @Nullable Icon getIcon() { 82 | return null; 83 | } 84 | 85 | @Override 86 | public boolean isNeedAppId() { 87 | return true; 88 | } 89 | 90 | @Override 91 | public @Nullable String getAppId() { 92 | return SettingsState.getInstance().getAppId(getKey()); 93 | } 94 | 95 | @Override 96 | public String getAppIdDisplay() { 97 | return "APP ID"; 98 | } 99 | 100 | @Override 101 | public boolean isNeedAppKey() { 102 | return true; 103 | } 104 | 105 | @Override 106 | public @Nullable String getAppKey() { 107 | return SettingsState.getInstance().getAppKey(getKey()); 108 | } 109 | 110 | @Override 111 | public String getAppKeyDisplay() { 112 | return "APP KEY"; 113 | } 114 | 115 | @Override 116 | public @Nullable String getApplyAppIdUrl() { 117 | return null; 118 | } 119 | 120 | @NotNull 121 | public String getRequestUrl(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 122 | throw new UnsupportedOperationException(); 123 | } 124 | 125 | @NotNull 126 | public List> getRequestParams(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 127 | return List.of(); 128 | } 129 | 130 | @NotNull 131 | public String getRequestBody(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 132 | return ""; 133 | } 134 | 135 | public void configureRequestBuilder(@NotNull RequestBuilder requestBuilder) { 136 | 137 | } 138 | 139 | @NotNull 140 | public String parsingResult(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text, @NotNull String resultText) { 141 | throw new UnsupportedOperationException(); 142 | } 143 | 144 | protected void checkSupportedLanguages(Lang fromLang, Lang toLang, String text) { 145 | List supportedLanguages = getSupportedLanguages(); 146 | if (!supportedLanguages.contains(toLang)) { 147 | throw new TranslationException(fromLang, toLang, text, toLang.getEnglishName() + " is not supported."); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/TranslationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate; 19 | 20 | import com.airsaid.localization.translate.lang.Lang; 21 | import com.intellij.openapi.diagnostic.Logger; 22 | 23 | import org.apache.http.HttpException; 24 | import org.jetbrains.annotations.NotNull; 25 | 26 | /** 27 | * @author airsaid 28 | */ 29 | public class TranslationException extends RuntimeException { 30 | 31 | private static final Logger LOG = Logger.getInstance(TranslationException.class); 32 | 33 | private final Lang fromLang; 34 | private final Lang toLang; 35 | private final String text; 36 | 37 | public TranslationException(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text, @NotNull Throwable cause) { 38 | super("Failed to translate \"" + text + "\" from " + fromLang.getEnglishName() + 39 | " to " + toLang.getEnglishName() + " with error: " + cause.getMessage(), cause); 40 | this.fromLang = fromLang; 41 | this.toLang = toLang; 42 | this.text = text; 43 | cause.printStackTrace(); 44 | LOG.error("TranslationException: " + cause.getMessage(), cause); 45 | } 46 | 47 | public TranslationException(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text, String message) { 48 | super("Failed to translate \"" + text + "\" from " + fromLang.getEnglishName() + 49 | " to " + toLang.getEnglishName() + " with error: " + message); 50 | this.fromLang = fromLang; 51 | this.toLang = toLang; 52 | this.text = text; 53 | LOG.error("TranslationException: ", message); 54 | } 55 | 56 | public Lang getFromLang() { 57 | return fromLang; 58 | } 59 | 60 | public Lang getToLang() { 61 | return toLang; 62 | } 63 | 64 | public String getText() { 65 | return text; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/TranslationResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate; 19 | 20 | import com.airsaid.localization.translate.impl.google.GoogleTranslationResult; 21 | import org.jetbrains.annotations.NotNull; 22 | 23 | /** 24 | * Translation results interface to obtain common translation result. 25 | * 26 | * @author airsaid 27 | * @see GoogleTranslationResult 28 | */ 29 | public interface TranslationResult { 30 | 31 | /** 32 | * Get a translation result of the specified text. 33 | * 34 | * @return translation result text. 35 | */ 36 | @NotNull 37 | String getTranslationResult(); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/Translator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate; 19 | 20 | import com.airsaid.localization.translate.impl.google.GoogleTranslator; 21 | import com.airsaid.localization.translate.lang.Lang; 22 | import org.jetbrains.annotations.NotNull; 23 | 24 | /** 25 | * The translator interface, the direct implementation class is {@link AbstractTranslator}, 26 | * and all translators should extends {@link AbstractTranslator} to avoid writing duplicate code. 27 | * 28 | * @author airsaid 29 | * @see AbstractTranslator 30 | * @see GoogleTranslator 31 | */ 32 | public interface Translator { 33 | 34 | /** 35 | * Invoke translation operation. 36 | * 37 | * @param fromLang the language of text. 38 | * @param toLang the language to be translated into. 39 | * @param text the text to be translated. 40 | * @return the translated text. 41 | * @throws TranslationException this exception is thrown if the translation failed. 42 | */ 43 | String doTranslate(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) throws TranslationException; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/TranslatorConfigurable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate; 19 | 20 | import com.airsaid.localization.translate.lang.Lang; 21 | import org.jetbrains.annotations.NotNull; 22 | import org.jetbrains.annotations.Nullable; 23 | 24 | import javax.swing.*; 25 | import java.util.List; 26 | 27 | /** 28 | * @author airsaid 29 | */ 30 | public interface TranslatorConfigurable { 31 | 32 | @NotNull 33 | String getKey(); 34 | 35 | @NotNull 36 | String getName(); 37 | 38 | @Nullable 39 | Icon getIcon(); 40 | 41 | @NotNull 42 | List getSupportedLanguages(); 43 | 44 | boolean isNeedAppId(); 45 | 46 | @Nullable 47 | String getAppId(); 48 | 49 | String getAppIdDisplay(); 50 | 51 | boolean isNeedAppKey(); 52 | 53 | @Nullable 54 | String getAppKey(); 55 | 56 | String getAppKeyDisplay(); 57 | 58 | @Nullable 59 | String getApplyAppIdUrl(); 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/ali/AliTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.ali; 19 | 20 | import com.airsaid.localization.translate.AbstractTranslator; 21 | import com.airsaid.localization.translate.TranslationException; 22 | import com.airsaid.localization.translate.lang.Lang; 23 | import com.airsaid.localization.translate.lang.Languages; 24 | import com.aliyun.alimt20181012.Client; 25 | import com.aliyun.alimt20181012.models.TranslateGeneralRequest; 26 | import com.aliyun.alimt20181012.models.TranslateGeneralResponse; 27 | import com.aliyun.alimt20181012.models.TranslateGeneralResponseBody; 28 | import com.aliyun.teaopenapi.models.Config; 29 | import com.aliyun.teautil.models.RuntimeOptions; 30 | import com.google.auto.service.AutoService; 31 | import icons.PluginIcons; 32 | import org.jetbrains.annotations.NotNull; 33 | import org.jetbrains.annotations.Nullable; 34 | 35 | import javax.swing.*; 36 | import java.util.LinkedList; 37 | import java.util.List; 38 | 39 | /** 40 | * @author airsaid 41 | */ 42 | @AutoService(AbstractTranslator.class) 43 | public class AliTranslator extends AbstractTranslator { 44 | private static final String KEY = "Ali"; 45 | private static final String ENDPOINT = "mt.aliyuncs.com"; 46 | private static final String APPLY_APP_ID_URL = "https://www.aliyun.com/product/ai/base_alimt"; 47 | 48 | private final Config config = new Config(); 49 | private List supportedLanguages; 50 | private Client client; 51 | 52 | @Override 53 | public @NotNull String getKey() { 54 | return KEY; 55 | } 56 | 57 | @Override 58 | public @NotNull String getName() { 59 | return "Ali"; 60 | } 61 | 62 | @Override 63 | public @Nullable Icon getIcon() { 64 | return PluginIcons.ALI_ICON; 65 | } 66 | 67 | @Override 68 | public @NotNull List getSupportedLanguages() { 69 | if (supportedLanguages == null) { 70 | supportedLanguages = new LinkedList<>(); 71 | final List languages = Languages.getLanguages(); 72 | for (int i = 1; i < languages.size(); i++) { 73 | Lang lang = languages.get(i); 74 | if (lang.equals(Languages.UKRAINIAN) || lang.equals(Languages.DARI)) { 75 | continue; 76 | } 77 | if (lang.equals(Languages.CHINESE_SIMPLIFIED)) { 78 | lang = lang.setTranslationCode("zh"); 79 | } else if (lang.equals(Languages.CHINESE_TRADITIONAL)) { 80 | lang = lang.setTranslationCode("zh-tw"); 81 | } else if (lang.equals(Languages.INDONESIAN)) { 82 | lang = lang.setTranslationCode("id"); 83 | } else if (lang.equals(Languages.CROATIAN)) { 84 | lang = lang.setTranslationCode("hbs"); 85 | } else if (lang.equals(Languages.HEBREW)) { 86 | lang = lang.setTranslationCode("he"); 87 | } 88 | supportedLanguages.add(lang); 89 | } 90 | } 91 | return supportedLanguages; 92 | } 93 | 94 | @Override 95 | public String getAppIdDisplay() { 96 | return "AccessKey ID"; 97 | } 98 | 99 | @Override 100 | public String getAppKeyDisplay() { 101 | return "AccessKey Secret"; 102 | } 103 | 104 | @Override 105 | public @Nullable String getApplyAppIdUrl() { 106 | return APPLY_APP_ID_URL; 107 | } 108 | 109 | @Override 110 | public String doTranslate(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) throws TranslationException { 111 | checkSupportedLanguages(fromLang, toLang, text); 112 | 113 | config.setAccessKeyId(getAppId()).setAccessKeySecret(getAppKey()).setEndpoint(ENDPOINT); 114 | if (client == null) { 115 | try { 116 | client = new Client(config); 117 | } catch (Exception e) { 118 | throw new TranslationException(fromLang, toLang, text, e); 119 | } 120 | } 121 | 122 | TranslateGeneralRequest request = new TranslateGeneralRequest() 123 | .setFormatType("text") 124 | .setSourceLanguage(fromLang.getTranslationCode()) 125 | .setTargetLanguage(toLang.getTranslationCode()) 126 | .setSourceText(text) 127 | .setScene("general"); 128 | RuntimeOptions runtime = new RuntimeOptions(); 129 | TranslateGeneralResponse response; 130 | try { 131 | response = client.translateGeneralWithOptions(request, runtime); 132 | } catch (Exception e) { 133 | throw new TranslationException(fromLang, toLang, text, e); 134 | } 135 | final TranslateGeneralResponseBody body = response.body; 136 | if (body.getCode() == 200) { 137 | return body.getData().translated; 138 | } else { 139 | throw new TranslationException(fromLang, toLang, text, body.getMessage() + "(" + body.getCode() + ")"); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/baidu/BaiduTranslationResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.baidu; 19 | 20 | import com.airsaid.localization.translate.TranslationResult; 21 | import com.google.gson.annotations.SerializedName; 22 | import com.intellij.openapi.util.text.StringUtil; 23 | import org.jetbrains.annotations.NotNull; 24 | 25 | import java.util.List; 26 | import java.util.Objects; 27 | 28 | /** 29 | * @author airsaid 30 | */ 31 | public class BaiduTranslationResult implements TranslationResult { 32 | private String from; 33 | private String to; 34 | @SerializedName("trans_result") 35 | private List contents; 36 | 37 | @SerializedName("error_code") 38 | private String errorCode; 39 | @SerializedName("error_msg") 40 | private String errorMsg; 41 | 42 | public String getFrom() { 43 | return from; 44 | } 45 | 46 | public void setFrom(String from) { 47 | this.from = from; 48 | } 49 | 50 | public String getTo() { 51 | return to; 52 | } 53 | 54 | public void setTo(String to) { 55 | this.to = to; 56 | } 57 | 58 | public List getContents() { 59 | return contents; 60 | } 61 | 62 | public void setContents(List contents) { 63 | this.contents = contents; 64 | } 65 | 66 | public String getErrorCode() { 67 | return errorCode; 68 | } 69 | 70 | public void setErrorCode(String errorCode) { 71 | this.errorCode = errorCode; 72 | } 73 | 74 | public String getErrorMsg() { 75 | return errorMsg; 76 | } 77 | 78 | public void setErrorMsg(String errorMsg) { 79 | this.errorMsg = errorMsg; 80 | } 81 | 82 | public boolean isSuccess() { 83 | String errorCode = getErrorCode(); 84 | return StringUtil.isEmpty(errorCode) || "52000".equals(getErrorCode()); 85 | } 86 | 87 | @Override 88 | public boolean equals(Object o) { 89 | if (this == o) return true; 90 | if (o == null || getClass() != o.getClass()) return false; 91 | BaiduTranslationResult that = (BaiduTranslationResult) o; 92 | return Objects.equals(from, that.from) && Objects.equals(to, that.to) && Objects.equals(contents, that.contents) && Objects.equals(errorCode, that.errorCode) && Objects.equals(errorMsg, that.errorMsg); 93 | } 94 | 95 | @Override 96 | public int hashCode() { 97 | return Objects.hash(from, to, contents, errorCode, errorMsg); 98 | } 99 | 100 | @Override 101 | public @NotNull String getTranslationResult() { 102 | List contents = getContents(); 103 | if (contents == null || contents.isEmpty()) { 104 | return ""; 105 | } 106 | String dst = contents.get(0).getDst(); 107 | return dst != null ? dst : ""; 108 | } 109 | 110 | @Override 111 | public String toString() { 112 | return "BaiduTranslationResult{" + 113 | "from='" + from + '\'' + 114 | ", to='" + to + '\'' + 115 | ", contents=" + contents + 116 | ", errorCode='" + errorCode + '\'' + 117 | ", errorMsg='" + errorMsg + '\'' + 118 | '}'; 119 | } 120 | 121 | public static class Content { 122 | private String src; 123 | private String dst; 124 | 125 | public String getSrc() { 126 | return src; 127 | } 128 | 129 | public void setSrc(String src) { 130 | this.src = src; 131 | } 132 | 133 | public String getDst() { 134 | return dst; 135 | } 136 | 137 | public void setDst(String dst) { 138 | this.dst = dst; 139 | } 140 | 141 | @Override 142 | public boolean equals(Object o) { 143 | if (this == o) return true; 144 | if (o == null || getClass() != o.getClass()) return false; 145 | Content content = (Content) o; 146 | return Objects.equals(src, content.src) && Objects.equals(dst, content.dst); 147 | } 148 | 149 | @Override 150 | public int hashCode() { 151 | return Objects.hash(src, dst); 152 | } 153 | 154 | @Override 155 | public String toString() { 156 | return "Content{" + 157 | "src='" + src + '\'' + 158 | ", dst='" + dst + '\'' + 159 | '}'; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/baidu/BaiduTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.baidu; 19 | 20 | import com.airsaid.localization.translate.AbstractTranslator; 21 | import com.airsaid.localization.translate.TranslationException; 22 | import com.airsaid.localization.translate.lang.Lang; 23 | import com.airsaid.localization.translate.lang.Languages; 24 | import com.airsaid.localization.translate.util.GsonUtil; 25 | import com.airsaid.localization.translate.util.MD5; 26 | import com.google.auto.service.AutoService; 27 | import com.intellij.openapi.util.Pair; 28 | import com.intellij.util.io.RequestBuilder; 29 | import icons.PluginIcons; 30 | import org.jetbrains.annotations.NotNull; 31 | import org.jetbrains.annotations.Nullable; 32 | 33 | import javax.swing.*; 34 | import java.util.ArrayList; 35 | import java.util.List; 36 | 37 | /** 38 | * @author airsaid 39 | */ 40 | @AutoService(AbstractTranslator.class) 41 | public class BaiduTranslator extends AbstractTranslator { 42 | private static final String KEY = "Baidu"; 43 | private static final String HOST_URL = "http://api.fanyi.baidu.com"; 44 | private static final String TRANSLATE_URL = HOST_URL.concat("/api/trans/vip/translate"); 45 | private static final String APPLY_APP_ID_URL = "http://api.fanyi.baidu.com/api/trans/product/desktop?req=developer"; 46 | 47 | private List supportedLanguages; 48 | 49 | @Override 50 | public @NotNull String getKey() { 51 | return KEY; 52 | } 53 | 54 | @Override 55 | public @NotNull String getName() { 56 | return "Baidu"; 57 | } 58 | 59 | @Override 60 | public @Nullable Icon getIcon() { 61 | return PluginIcons.BAIDU_ICON; 62 | } 63 | 64 | @Override 65 | public @NotNull List getSupportedLanguages() { 66 | if (supportedLanguages == null) { 67 | supportedLanguages = new ArrayList<>(); 68 | supportedLanguages.add(Languages.CHINESE_SIMPLIFIED.setTranslationCode("zh")); 69 | supportedLanguages.add(Languages.ENGLISH); 70 | supportedLanguages.add(Languages.JAPANESE.setTranslationCode("jp")); 71 | supportedLanguages.add(Languages.KOREAN.setTranslationCode("kor")); 72 | supportedLanguages.add(Languages.FRENCH.setTranslationCode("fra")); 73 | supportedLanguages.add(Languages.SPANISH.setTranslationCode("spa")); 74 | supportedLanguages.add(Languages.THAI); 75 | supportedLanguages.add(Languages.ARABIC.setTranslationCode("ara")); 76 | supportedLanguages.add(Languages.RUSSIAN); 77 | supportedLanguages.add(Languages.PORTUGUESE); 78 | supportedLanguages.add(Languages.GERMAN); 79 | supportedLanguages.add(Languages.ITALIAN); 80 | supportedLanguages.add(Languages.GREEK); 81 | supportedLanguages.add(Languages.DUTCH); 82 | supportedLanguages.add(Languages.POLISH); 83 | supportedLanguages.add(Languages.BULGARIAN.setTranslationCode("bul")); 84 | supportedLanguages.add(Languages.ESTONIAN.setTranslationCode("est")); 85 | supportedLanguages.add(Languages.DANISH.setTranslationCode("dan")); 86 | supportedLanguages.add(Languages.FINNISH.setTranslationCode("fin")); 87 | supportedLanguages.add(Languages.CZECH); 88 | supportedLanguages.add(Languages.ROMANIAN.setTranslationCode("rom")); 89 | supportedLanguages.add(Languages.SLOVENIAN.setTranslationCode("slo")); 90 | supportedLanguages.add(Languages.SWEDISH.setTranslationCode("swe")); 91 | supportedLanguages.add(Languages.HUNGARIAN); 92 | supportedLanguages.add(Languages.CHINESE_TRADITIONAL.setTranslationCode("cht")); 93 | supportedLanguages.add(Languages.VIETNAMESE.setTranslationCode("vie")); 94 | } 95 | return supportedLanguages; 96 | } 97 | 98 | @Override 99 | public @Nullable String getApplyAppIdUrl() { 100 | return APPLY_APP_ID_URL; 101 | } 102 | 103 | @Override 104 | public @NotNull String getRequestUrl(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 105 | return TRANSLATE_URL; 106 | } 107 | 108 | @Override 109 | public @NotNull List> getRequestParams(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 110 | String salt = String.valueOf(System.currentTimeMillis()); 111 | String appId = getAppId(); 112 | String securityKey = getAppKey(); 113 | String sign = MD5.md5(appId + text + salt + securityKey); 114 | List> params = new ArrayList<>(); 115 | params.add(Pair.create("from", fromLang.getTranslationCode())); 116 | params.add(Pair.create("to", toLang.getTranslationCode())); 117 | params.add(Pair.create("appid", appId)); 118 | params.add(Pair.create("salt", salt)); 119 | params.add(Pair.create("sign", sign)); 120 | params.add(Pair.create("q", text)); 121 | return params; 122 | } 123 | 124 | @Override 125 | public void configureRequestBuilder(@NotNull RequestBuilder requestBuilder) { 126 | requestBuilder.tuner(connection -> connection.setRequestProperty("Referer", HOST_URL)); 127 | } 128 | 129 | @Override 130 | public @NotNull String parsingResult(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text, @NotNull String resultText) { 131 | LOG.info("parsingResult: " + resultText); 132 | BaiduTranslationResult baiduTranslationResult = GsonUtil.getInstance().getGson().fromJson(resultText, BaiduTranslationResult.class); 133 | if (baiduTranslationResult.isSuccess()) { 134 | return baiduTranslationResult.getTranslationResult(); 135 | } else { 136 | String message = baiduTranslationResult.getErrorMsg().concat("(").concat(baiduTranslationResult.getErrorCode()).concat(")"); 137 | throw new TranslationException(fromLang, toLang, text, message); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLProTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.deepl; 19 | 20 | import com.airsaid.localization.translate.AbstractTranslator; 21 | import com.airsaid.localization.translate.lang.Lang; 22 | import com.google.auto.service.AutoService; 23 | import org.jetbrains.annotations.NotNull; 24 | 25 | /** 26 | * @author airsaid 27 | */ 28 | @AutoService(AbstractTranslator.class) 29 | public class DeepLProTranslator extends DeepLTranslator { 30 | 31 | private static final String KEY = "DeepLPro"; 32 | private static final String HOST_URL = "https://api.deepl.com/v2"; 33 | private static final String TRANSLATE_URL = HOST_URL.concat("/translate"); 34 | 35 | @Override 36 | public @NotNull String getKey() { 37 | return KEY; 38 | } 39 | 40 | @Override 41 | public @NotNull String getName() { 42 | return KEY; 43 | } 44 | 45 | @Override 46 | public @NotNull String getRequestUrl(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 47 | return TRANSLATE_URL; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLTranslationResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.deepl; 19 | 20 | import com.airsaid.localization.translate.TranslationResult; 21 | import org.jetbrains.annotations.NotNull; 22 | 23 | import java.util.List; 24 | import java.util.Objects; 25 | 26 | /** 27 | * @author musagil 28 | */ 29 | public class DeepLTranslationResult implements TranslationResult { 30 | 31 | private List translations; 32 | 33 | @Override 34 | public @NotNull String getTranslationResult() { 35 | if (translations != null && !translations.isEmpty()) { 36 | String result = translations.get(0).getText(); 37 | return result != null ? result : ""; 38 | } 39 | return ""; 40 | } 41 | 42 | public List getTranslations() { 43 | return translations; 44 | } 45 | 46 | public void setTranslations(List translations) { 47 | this.translations = translations; 48 | } 49 | 50 | @Override 51 | public boolean equals(Object o) { 52 | if (this == o) return true; 53 | if (o == null || getClass() != o.getClass()) return false; 54 | DeepLTranslationResult that = (DeepLTranslationResult) o; 55 | return Objects.equals(translations, that.translations); 56 | } 57 | 58 | @Override 59 | public int hashCode() { 60 | return Objects.hash(translations); 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return "DeepLTranslationResult{" + 66 | "translations=" + translations + 67 | '}'; 68 | } 69 | 70 | public static class Translation { 71 | private String text; 72 | private String to; 73 | 74 | public String getText() { 75 | return text; 76 | } 77 | 78 | public void setText(String text) { 79 | this.text = text; 80 | } 81 | 82 | public String getTo() { 83 | return to; 84 | } 85 | 86 | public void setTo(String to) { 87 | this.to = to; 88 | } 89 | 90 | @Override 91 | public boolean equals(Object o) { 92 | if (this == o) return true; 93 | if (o == null || getClass() != o.getClass()) return false; 94 | Translation that = (Translation) o; 95 | return Objects.equals(text, that.text) && Objects.equals(to, that.to); 96 | } 97 | 98 | @Override 99 | public int hashCode() { 100 | return Objects.hash(text, to); 101 | } 102 | 103 | @Override 104 | public String toString() { 105 | return "Translation{" + 106 | "text='" + text + '\'' + 107 | ", to='" + to + '\'' + 108 | '}'; 109 | } 110 | } 111 | 112 | public static class Error { 113 | private String code; 114 | private String message; 115 | 116 | public String getCode() { 117 | return code; 118 | } 119 | 120 | public void setCode(String code) { 121 | this.code = code; 122 | } 123 | 124 | public String getMessage() { 125 | return message; 126 | } 127 | 128 | public void setMessage(String message) { 129 | this.message = message; 130 | } 131 | 132 | @Override 133 | public boolean equals(Object o) { 134 | if (this == o) return true; 135 | if (o == null || getClass() != o.getClass()) return false; 136 | Error error = (Error) o; 137 | return Objects.equals(code, error.code) && Objects.equals(message, error.message); 138 | } 139 | 140 | @Override 141 | public int hashCode() { 142 | return Objects.hash(code, message); 143 | } 144 | 145 | @Override 146 | public String toString() { 147 | return "Error{" + 148 | "code='" + code + '\'' + 149 | ", message='" + message + '\'' + 150 | '}'; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.deepl; 19 | 20 | import com.airsaid.localization.translate.AbstractTranslator; 21 | import com.airsaid.localization.translate.lang.Lang; 22 | import com.airsaid.localization.translate.lang.Languages; 23 | import com.airsaid.localization.translate.util.GsonUtil; 24 | import com.airsaid.localization.translate.util.UrlBuilder; 25 | import com.google.auto.service.AutoService; 26 | import com.intellij.openapi.diagnostic.Logger; 27 | import com.intellij.openapi.util.Pair; 28 | import com.intellij.util.io.RequestBuilder; 29 | import icons.PluginIcons; 30 | import org.jetbrains.annotations.NotNull; 31 | import org.jetbrains.annotations.Nullable; 32 | 33 | import javax.swing.*; 34 | import java.util.ArrayList; 35 | import java.util.List; 36 | 37 | /** 38 | * @author musagil 39 | */ 40 | @AutoService(AbstractTranslator.class) 41 | public class DeepLTranslator extends AbstractTranslator { 42 | 43 | private static final Logger LOG = Logger.getInstance(DeepLTranslator.class); 44 | 45 | private static final String KEY = "DeepL"; 46 | private static final String HOST_URL = "https://api-free.deepl.com/v2"; 47 | private static final String TRANSLATE_URL = HOST_URL.concat("/translate"); 48 | private static final String APPLY_APP_ID_URL = "https://www.deepl.com/pro-api?cta=header-pro-api/"; 49 | 50 | private List supportedLanguages; 51 | 52 | @Override 53 | public @NotNull String getKey() { 54 | return KEY; 55 | } 56 | 57 | @Override 58 | public @NotNull String getName() { 59 | return "DeepL"; 60 | } 61 | 62 | @Override 63 | public @Nullable Icon getIcon() { 64 | return PluginIcons.DEEP_L_ICON; 65 | } 66 | 67 | @Override 68 | public boolean isNeedAppId() { 69 | return false; 70 | } 71 | 72 | @Override 73 | public @NotNull List getSupportedLanguages() { 74 | if (supportedLanguages == null) { 75 | supportedLanguages = new ArrayList<>(); 76 | supportedLanguages.add(Languages.BULGARIAN); 77 | supportedLanguages.add(Languages.CZECH); 78 | supportedLanguages.add(Languages.DANISH); 79 | supportedLanguages.add(Languages.GERMAN); 80 | supportedLanguages.add(Languages.GREEK); 81 | supportedLanguages.add(new Lang(118, "en-gb", "English (British)", "English (British)")); 82 | supportedLanguages.add(new Lang(119, "en-us", "English (American)", "English (American)")); 83 | supportedLanguages.add(Languages.SPANISH); 84 | supportedLanguages.add(Languages.ESTONIAN); 85 | supportedLanguages.add(Languages.FINNISH); 86 | supportedLanguages.add(Languages.FRENCH); 87 | supportedLanguages.add(Languages.HUNGARIAN); 88 | supportedLanguages.add(new Lang(98, "id", "Indonesia", "Indonesian")); 89 | supportedLanguages.add(Languages.ITALIAN); 90 | supportedLanguages.add(Languages.JAPANESE); 91 | supportedLanguages.add(Languages.KOREAN.setTranslationCode("KO")); 92 | supportedLanguages.add(Languages.LITHUANIAN); 93 | supportedLanguages.add(Languages.LATVIAN); 94 | supportedLanguages.add(Languages.NORWEGIAN.setTranslationCode("NB")); 95 | supportedLanguages.add(Languages.DUTCH); 96 | supportedLanguages.add(Languages.POLISH); 97 | supportedLanguages.add(new Lang(120, "pt-br", "Portuguese (Brazilian)", "Portuguese (Brazilian)")); 98 | supportedLanguages.add(new Lang(121, "pt-pt", "Portuguese (European)", "Portuguese (European)")); 99 | supportedLanguages.add(Languages.ROMANIAN); 100 | supportedLanguages.add(Languages.RUSSIAN); 101 | supportedLanguages.add(Languages.SLOVAK); 102 | supportedLanguages.add(Languages.SLOVENIAN); 103 | supportedLanguages.add(Languages.SWEDISH); 104 | supportedLanguages.add(Languages.TURKISH); 105 | supportedLanguages.add(Languages.UKRAINIAN); 106 | supportedLanguages.add(new Lang(104, "zh", "简体中文", "Chinese Simplified")); 107 | } 108 | return supportedLanguages; 109 | } 110 | 111 | @Override 112 | public String getAppKeyDisplay() { 113 | return "KEY"; 114 | } 115 | 116 | @Override 117 | public @Nullable String getApplyAppIdUrl() { 118 | return APPLY_APP_ID_URL; 119 | } 120 | 121 | @Override 122 | public @NotNull String getRequestUrl(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 123 | return new UrlBuilder(TRANSLATE_URL).build(); 124 | } 125 | 126 | @Override 127 | public @NotNull List> getRequestParams(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 128 | List> params = new ArrayList<>(); 129 | params.add(Pair.create("text", text)); 130 | params.add(Pair.create("target_lang", toLang.getCode())); 131 | return params; 132 | } 133 | 134 | @Override 135 | public void configureRequestBuilder(@NotNull RequestBuilder requestBuilder) { 136 | requestBuilder.tuner(connection -> { 137 | connection.setRequestProperty("Authorization", "DeepL-Auth-Key " + getAppKey()); 138 | connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); 139 | }); 140 | } 141 | 142 | @Override 143 | public @NotNull String parsingResult(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text, @NotNull String resultText) { 144 | LOG.info("parsingResult: " + resultText); 145 | return GsonUtil.getInstance().getGson().fromJson(resultText, DeepLTranslationResult.class).getTranslationResult(); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/google/AbsGoogleTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.google; 19 | 20 | import com.airsaid.localization.translate.AbstractTranslator; 21 | import com.airsaid.localization.translate.lang.Lang; 22 | import com.airsaid.localization.translate.lang.Languages; 23 | import icons.PluginIcons; 24 | import org.jetbrains.annotations.NotNull; 25 | 26 | import javax.swing.*; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | /** 31 | * @author airsaid 32 | */ 33 | public abstract class AbsGoogleTranslator extends AbstractTranslator { 34 | 35 | protected List supportedLanguages; 36 | 37 | @Override 38 | public @NotNull Icon getIcon() { 39 | return PluginIcons.GOOGLE_ICON; 40 | } 41 | 42 | @Override 43 | @NotNull 44 | public List getSupportedLanguages() { 45 | if (supportedLanguages == null) { 46 | List languages = Languages.getLanguages(); 47 | supportedLanguages = new ArrayList<>(104); 48 | for (int i = 1; i <= 104; i++) { 49 | Lang lang = languages.get(i); 50 | if (lang.equals(Languages.CHINESE_SIMPLIFIED)) { 51 | lang = lang.setTranslationCode("zh-CN"); 52 | } else if (lang.equals(Languages.CHINESE_TRADITIONAL)) { 53 | lang = lang.setTranslationCode("zh-TW"); 54 | } else if (lang.equals(Languages.FILIPINO)) { 55 | lang = lang.setTranslationCode("tl"); 56 | } else if (lang.equals(Languages.INDONESIAN)) { 57 | lang = lang.setTranslationCode("id"); 58 | } else if (lang.equals(Languages.JAVANESE)) { 59 | lang = lang.setTranslationCode("jw"); 60 | } 61 | supportedLanguages.add(lang); 62 | } 63 | } 64 | return supportedLanguages; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/google/GoogleToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.google; 19 | 20 | import com.airsaid.localization.translate.util.AgentUtil; 21 | import com.intellij.openapi.diagnostic.Logger; 22 | import com.intellij.openapi.util.Pair; 23 | import com.intellij.util.io.HttpRequests; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.Random; 28 | import java.util.regex.Matcher; 29 | import java.util.regex.Pattern; 30 | 31 | /** 32 | * @author airsaid 33 | */ 34 | public class GoogleToken { 35 | 36 | private static final Logger LOG = Logger.getInstance(GoogleToken.class); 37 | 38 | private static final int MIM = 60 * 60 * 1000; 39 | private static final Random GENERATOR = new Random(); 40 | private static final Pattern TKK_PATTERN = Pattern.compile("tkk='(\\d+).(-?\\d+)'"); 41 | private static final String ELEMENT_URL = "%s/translate_a/element.js"; 42 | 43 | private static Pair sInnerValue = Pair.create(0L, 0L); 44 | private static boolean sNeedUpdate = true; 45 | 46 | public static String getToken(String text) { 47 | return getToken(text, getDefaultTKK()); 48 | } 49 | 50 | public static String getToken(String text, Pair tkk) { 51 | int length = text.length(); 52 | List a = new ArrayList<>(); 53 | int b = 0; 54 | char[] ch = text.toCharArray(); 55 | while (b < length) { 56 | int c = ch[b]; 57 | if (128 > c) { 58 | a.add((long) c); 59 | } else { 60 | if (2048 > c) { 61 | a.add((long) (c >> 6 | 192)); 62 | } else { 63 | if (55296 == (c & 64512) && b + 1 < length && 56320 == (ch[b + 1] & 64512)) { 64 | c = 65536 + ((c & 1023) << 10) + (ch[++b] & 1023); 65 | a.add((long) (c >> 18 | 240)); 66 | a.add((long) (c >> 12 & 63 | 128)); 67 | } else { 68 | a.add((long) (c >> 12 | 224)); 69 | } 70 | a.add((long) (c >> 6 & 63 | 128)); 71 | } 72 | a.add((long) (c & 63 | 128)); 73 | } 74 | b++; 75 | } 76 | 77 | long d = tkk.first; 78 | long e = tkk.second; 79 | long f = d; 80 | for (Long h : a) { 81 | f += h; 82 | f = fun(f, "+-a^+6"); 83 | } 84 | 85 | f = fun(f, "+-3^+b+-f"); 86 | f = f ^ e; 87 | if (0 > f) { 88 | f = (f & Integer.MAX_VALUE) + Integer.MAX_VALUE + 1; 89 | } 90 | f = (long) (f % 1E6); 91 | 92 | return f + "." + (f ^ d); 93 | } 94 | 95 | private static Long fun(Long a, String b) { 96 | long g = a; 97 | char[] ch = b.toCharArray(); 98 | for (int c = 0; c < ch.length - 1; c += 3) { 99 | char d = ch[c + 2]; 100 | int e = 'a' <= d ? (d - 87) : d - '0'; 101 | long f = '+' == ch[c + 1] ? g >>> e : g << e; 102 | g = '+' == ch[c] ? g + f & ((long) Integer.MAX_VALUE * 2 + 1) : g ^ f; 103 | } 104 | return g; 105 | } 106 | 107 | private static Pair getDefaultTKK() { 108 | long now = System.currentTimeMillis() / MIM; 109 | long curVal = sInnerValue.first; 110 | if (!sNeedUpdate && now == curVal) { 111 | return sInnerValue; 112 | } 113 | 114 | Pair newTKK = getTKKFromGoogle(); 115 | sNeedUpdate = newTKK == null; 116 | sInnerValue = newTKK != null ? newTKK : Pair.create(now, Math.abs((long) GENERATOR.nextInt()) + (long) GENERATOR.nextInt()); 117 | 118 | return sInnerValue; 119 | } 120 | 121 | private static Pair getTKKFromGoogle() { 122 | try { 123 | String url = String.format(ELEMENT_URL, GoogleTranslator.HOST_URL); 124 | LOG.info("getTKKFromGoogle url: " + url); 125 | String elementJs = HttpRequests.request(url) 126 | .userAgent(AgentUtil.getUserAgent()) 127 | .tuner(connection -> connection.setRequestProperty("Referer", GoogleTranslator.HOST_URL)) 128 | .readString(); 129 | Matcher matcher = TKK_PATTERN.matcher(elementJs); 130 | if (matcher.find()) { 131 | long value1 = Long.parseLong(matcher.group(1)); 132 | long value2 = Long.parseLong(matcher.group(2)); 133 | LOG.info(String.format("TKK: %d.%d", value1, value2)); 134 | return Pair.create(value1, value1); 135 | } 136 | } catch (Exception e) { 137 | e.printStackTrace(); 138 | LOG.warn("TKK get failed.", e); 139 | } 140 | return null; 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/google/GoogleTranslationResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.google; 19 | 20 | import com.airsaid.localization.translate.TranslationResult; 21 | import com.google.gson.annotations.SerializedName; 22 | import org.jetbrains.annotations.NotNull; 23 | 24 | import java.util.List; 25 | import java.util.Objects; 26 | 27 | /** 28 | * @author airsaid 29 | */ 30 | public class GoogleTranslationResult implements TranslationResult { 31 | @SerializedName("src") 32 | private String sourceCode; 33 | 34 | private List sentences; 35 | 36 | public GoogleTranslationResult() { 37 | 38 | } 39 | 40 | public GoogleTranslationResult(String sourceCode, List sentences) { 41 | this.sourceCode = sourceCode; 42 | this.sentences = sentences; 43 | } 44 | 45 | public String getSourceCode() { 46 | return sourceCode; 47 | } 48 | 49 | public void setSourceCode(String sourceCode) { 50 | this.sourceCode = sourceCode; 51 | } 52 | 53 | public List getSentences() { 54 | return sentences; 55 | } 56 | 57 | public void setSentences(List sentences) { 58 | this.sentences = sentences; 59 | } 60 | 61 | @Override 62 | public boolean equals(Object o) { 63 | if (this == o) return true; 64 | if (o == null || getClass() != o.getClass()) return false; 65 | GoogleTranslationResult that = (GoogleTranslationResult) o; 66 | return Objects.equals(sourceCode, that.sourceCode) && Objects.equals(sentences, that.sentences); 67 | } 68 | 69 | @Override 70 | public int hashCode() { 71 | return Objects.hash(sourceCode, sentences); 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | return "GoogleTranslationResult{" + 77 | "sourceCode='" + sourceCode + '\'' + 78 | ", sentences=" + sentences + 79 | '}'; 80 | } 81 | 82 | @Override 83 | public @NotNull String getTranslationResult() { 84 | List sentences = getSentences(); 85 | if (sentences == null || sentences.isEmpty()) { 86 | return ""; 87 | } 88 | StringBuilder result = new StringBuilder(); 89 | for (Sentences sentence : sentences) { 90 | String trans = sentence.getTrans(); 91 | if (trans != null) result.append(trans); 92 | } 93 | return result.toString(); 94 | } 95 | 96 | public static class Sentences { 97 | private String trans; 98 | 99 | @SerializedName("orig") 100 | private String origin; 101 | 102 | private int backend; 103 | 104 | public Sentences() { 105 | 106 | } 107 | 108 | public Sentences(String trans, String origin, int backend) { 109 | this.trans = trans; 110 | this.origin = origin; 111 | this.backend = backend; 112 | } 113 | 114 | public String getTrans() { 115 | return trans; 116 | } 117 | 118 | public void setTrans(String trans) { 119 | this.trans = trans; 120 | } 121 | 122 | public String getOrigin() { 123 | return origin; 124 | } 125 | 126 | public void setOrigin(String origin) { 127 | this.origin = origin; 128 | } 129 | 130 | public int getBackend() { 131 | return backend; 132 | } 133 | 134 | public void setBackend(int backend) { 135 | this.backend = backend; 136 | } 137 | 138 | @Override 139 | public boolean equals(Object o) { 140 | if (this == o) return true; 141 | if (o == null || getClass() != o.getClass()) return false; 142 | Sentences sentences = (Sentences) o; 143 | return backend == sentences.backend && Objects.equals(trans, sentences.trans) && Objects.equals(origin, sentences.origin); 144 | } 145 | 146 | @Override 147 | public int hashCode() { 148 | return Objects.hash(trans, origin, backend); 149 | } 150 | 151 | @Override 152 | public String toString() { 153 | return "Sentences{" + 154 | "trans='" + trans + '\'' + 155 | ", origin='" + origin + '\'' + 156 | ", backend=" + backend + 157 | '}'; 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/google/GoogleTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.google; 19 | 20 | import com.airsaid.localization.translate.AbstractTranslator; 21 | import com.airsaid.localization.translate.lang.Lang; 22 | import com.airsaid.localization.translate.util.AgentUtil; 23 | import com.airsaid.localization.translate.util.GsonUtil; 24 | import com.airsaid.localization.translate.util.UrlBuilder; 25 | import com.google.auto.service.AutoService; 26 | import com.intellij.openapi.util.Pair; 27 | import com.intellij.util.io.RequestBuilder; 28 | import org.jetbrains.annotations.NotNull; 29 | 30 | import java.util.ArrayList; 31 | import java.util.List; 32 | 33 | /** 34 | * @author airsaid 35 | */ 36 | @AutoService(AbstractTranslator.class) 37 | public class GoogleTranslator extends AbsGoogleTranslator { 38 | public static final String KEY = "Google"; 39 | 40 | public static final String HOST_URL = "https://translate.googleapis.com"; 41 | private static final String BASE_URL = HOST_URL.concat("/translate_a/single"); 42 | 43 | @Override 44 | public @NotNull String getKey() { 45 | return KEY; 46 | } 47 | 48 | @Override 49 | public @NotNull String getName() { 50 | return "Google"; 51 | } 52 | 53 | @Override 54 | public boolean isNeedAppId() { 55 | return false; 56 | } 57 | 58 | @Override 59 | public boolean isNeedAppKey() { 60 | return false; 61 | } 62 | 63 | @Override 64 | public @NotNull String getRequestUrl(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 65 | return new UrlBuilder(BASE_URL) 66 | .addQueryParameter("sl", fromLang.getTranslationCode()) // source language code (auto for auto detection) 67 | .addQueryParameter("tl", toLang.getTranslationCode()) // translation language 68 | .addQueryParameter("client", "gtx") // client of request (guess) 69 | .addQueryParameters("dt", "t") // specify what to return 70 | .addQueryParameter("dj", "1") // json response with names 71 | .addQueryParameter("ie", "UTF-8") // input encoding 72 | .addQueryParameter("oe", "UTF-8") // output encoding 73 | .addQueryParameter("tk", GoogleToken.getToken(text)) // translate token 74 | .build(); 75 | } 76 | 77 | @Override 78 | public @NotNull List> getRequestParams(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 79 | List> params = new ArrayList<>(); 80 | params.add(Pair.create("q", text)); 81 | return params; 82 | } 83 | 84 | @Override 85 | public void configureRequestBuilder(@NotNull RequestBuilder requestBuilder) { 86 | requestBuilder.userAgent(AgentUtil.getUserAgent()) 87 | .tuner(connection -> connection.setRequestProperty("Referer", GoogleTranslator.HOST_URL)); 88 | } 89 | 90 | @Override 91 | public @NotNull String parsingResult(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text, @NotNull String resultText) { 92 | LOG.info("parsingResult: " + resultText); 93 | GoogleTranslationResult googleTranslationResult = GsonUtil.getInstance().getGson().fromJson(resultText, GoogleTranslationResult.class); 94 | return googleTranslationResult.getTranslationResult(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/googleapi/GoogleApiTranslationResult.java: -------------------------------------------------------------------------------- 1 | package com.airsaid.localization.translate.impl.googleapi; 2 | 3 | import com.airsaid.localization.translate.TranslationResult; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.Arrays; 7 | 8 | /** 9 | * @author airsaid 10 | */ 11 | public class GoogleApiTranslationResult implements TranslationResult { 12 | public Data data; 13 | public Error error; 14 | 15 | public boolean isSuccess() { 16 | return data != null && error == null; 17 | } 18 | 19 | @Override 20 | public @NotNull String getTranslationResult() { 21 | return data.translations[0].translatedText; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "GoogleApiTranslationResult{" + 27 | "data=" + data + 28 | ", error=" + error + 29 | '}'; 30 | } 31 | 32 | public static class Data { 33 | public Translation[] translations; 34 | 35 | @Override 36 | public String toString() { 37 | return "Data{" + 38 | "translations=" + Arrays.toString(translations) + 39 | '}'; 40 | } 41 | 42 | public static class Translation { 43 | public String translatedText; 44 | public String detectedSourceLanguage; 45 | 46 | @Override 47 | public String toString() { 48 | return "Translation{" + 49 | "translatedText='" + translatedText + '\'' + 50 | ", detectedSourceLanguage='" + detectedSourceLanguage + '\'' + 51 | '}'; 52 | } 53 | } 54 | } 55 | 56 | public static class Error { 57 | public int code; 58 | public String message; 59 | 60 | @Override 61 | public String toString() { 62 | return "Error{" + 63 | "code=" + code + 64 | ", message='" + message + '\'' + 65 | '}'; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/googleapi/GoogleApiTranslator.java: -------------------------------------------------------------------------------- 1 | package com.airsaid.localization.translate.impl.googleapi; 2 | 3 | import com.airsaid.localization.translate.AbstractTranslator; 4 | import com.airsaid.localization.translate.TranslationException; 5 | import com.airsaid.localization.translate.impl.google.AbsGoogleTranslator; 6 | import com.airsaid.localization.translate.lang.Lang; 7 | import com.airsaid.localization.translate.util.GsonUtil; 8 | import com.google.auto.service.AutoService; 9 | import com.intellij.openapi.util.Pair; 10 | import com.intellij.util.io.RequestBuilder; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * @author airsaid 19 | */ 20 | @AutoService(AbstractTranslator.class) 21 | public class GoogleApiTranslator extends AbsGoogleTranslator { 22 | private static final String KEY = "GoogleApi"; 23 | private static final String HOST_URL = "https://translation.googleapis.com"; 24 | private static final String TRANSLATE_URL = HOST_URL.concat("/language/translate/v2"); 25 | private static final String APPLY_APP_ID_URL = "https://cloud.google.com/translate"; 26 | 27 | @Override 28 | public @NotNull String getKey() { 29 | return KEY; 30 | } 31 | 32 | @Override 33 | public @NotNull String getName() { 34 | return "Google (API)"; 35 | } 36 | 37 | @Override 38 | public @NotNull String getRequestUrl(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 39 | return TRANSLATE_URL; 40 | } 41 | 42 | @Override 43 | public String getAppKeyDisplay() { 44 | return "API Key"; 45 | } 46 | 47 | @Nullable 48 | @Override 49 | public String getApplyAppIdUrl() { 50 | return APPLY_APP_ID_URL; 51 | } 52 | 53 | @Override 54 | public boolean isNeedAppId() { 55 | return false; 56 | } 57 | 58 | @Override 59 | public @NotNull List> getRequestParams(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 60 | List> params = new ArrayList<>(); 61 | params.add(Pair.create("q", text)); 62 | params.add(Pair.create("target", toLang.getTranslationCode())); 63 | params.add(Pair.create("key", getAppKey())); 64 | params.add(Pair.create("format", "text")); 65 | return params; 66 | } 67 | 68 | @Override 69 | public void configureRequestBuilder(@NotNull RequestBuilder requestBuilder) { 70 | requestBuilder.tuner(connection -> connection.setRequestProperty("Referer", HOST_URL)); 71 | } 72 | 73 | @Override 74 | public @NotNull String parsingResult(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text, @NotNull String resultText) { 75 | LOG.info("parsingResult: " + resultText); 76 | GoogleApiTranslationResult result = GsonUtil.getInstance().getGson().fromJson(resultText, GoogleApiTranslationResult.class); 77 | if (result.isSuccess()) { 78 | return result.getTranslationResult(); 79 | } else { 80 | String message; 81 | if (result.error != null) { 82 | message = result.error.message.concat("(").concat(String.valueOf(result.error.code)).concat(")"); 83 | } else { 84 | message = "Unknown error"; 85 | } 86 | throw new TranslationException(fromLang, toLang, text, message); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/microsoft/MicrosoftTranslationResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.microsoft; 19 | 20 | import com.airsaid.localization.translate.TranslationResult; 21 | import org.jetbrains.annotations.NotNull; 22 | 23 | import java.util.List; 24 | import java.util.Objects; 25 | 26 | /** 27 | * @author airsaid 28 | */ 29 | public class MicrosoftTranslationResult implements TranslationResult { 30 | 31 | private List translations; 32 | 33 | @Override 34 | public @NotNull String getTranslationResult() { 35 | if (translations != null && !translations.isEmpty()) { 36 | String result = translations.get(0).getText(); 37 | return result != null ? result : ""; 38 | } 39 | return ""; 40 | } 41 | 42 | public List getTranslations() { 43 | return translations; 44 | } 45 | 46 | public void setTranslations(List translations) { 47 | this.translations = translations; 48 | } 49 | 50 | @Override 51 | public boolean equals(Object o) { 52 | if (this == o) return true; 53 | if (o == null || getClass() != o.getClass()) return false; 54 | MicrosoftTranslationResult that = (MicrosoftTranslationResult) o; 55 | return Objects.equals(translations, that.translations); 56 | } 57 | 58 | @Override 59 | public int hashCode() { 60 | return Objects.hash(translations); 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return "MicrosoftTranslationResult{" + 66 | "translations=" + translations + 67 | '}'; 68 | } 69 | 70 | public static class Translation { 71 | private String text; 72 | private String to; 73 | 74 | public String getText() { 75 | return text; 76 | } 77 | 78 | public void setText(String text) { 79 | this.text = text; 80 | } 81 | 82 | public String getTo() { 83 | return to; 84 | } 85 | 86 | public void setTo(String to) { 87 | this.to = to; 88 | } 89 | 90 | @Override 91 | public boolean equals(Object o) { 92 | if (this == o) return true; 93 | if (o == null || getClass() != o.getClass()) return false; 94 | Translation that = (Translation) o; 95 | return Objects.equals(text, that.text) && Objects.equals(to, that.to); 96 | } 97 | 98 | @Override 99 | public int hashCode() { 100 | return Objects.hash(text, to); 101 | } 102 | 103 | @Override 104 | public String toString() { 105 | return "Translation{" + 106 | "text='" + text + '\'' + 107 | ", to='" + to + '\'' + 108 | '}'; 109 | } 110 | } 111 | 112 | public static class Error { 113 | private String code; 114 | private String message; 115 | 116 | public String getCode() { 117 | return code; 118 | } 119 | 120 | public void setCode(String code) { 121 | this.code = code; 122 | } 123 | 124 | public String getMessage() { 125 | return message; 126 | } 127 | 128 | public void setMessage(String message) { 129 | this.message = message; 130 | } 131 | 132 | @Override 133 | public boolean equals(Object o) { 134 | if (this == o) return true; 135 | if (o == null || getClass() != o.getClass()) return false; 136 | Error error = (Error) o; 137 | return Objects.equals(code, error.code) && Objects.equals(message, error.message); 138 | } 139 | 140 | @Override 141 | public int hashCode() { 142 | return Objects.hash(code, message); 143 | } 144 | 145 | @Override 146 | public String toString() { 147 | return "Error{" + 148 | "code='" + code + '\'' + 149 | ", message='" + message + '\'' + 150 | '}'; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/openai/ChatGPTMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.openai; 19 | 20 | public class ChatGPTMessage { 21 | private String role; 22 | private String content; 23 | 24 | public ChatGPTMessage(String role, String content) { 25 | this.role = role; 26 | this.content = content; 27 | } 28 | 29 | public String getRole() { 30 | return role; 31 | } 32 | 33 | public void setRole(String role) { 34 | this.role = role; 35 | } 36 | 37 | public String getContent() { 38 | return content; 39 | } 40 | 41 | public void setContent(String content) { 42 | this.content = content; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/openai/ChatGPTTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.openai; 19 | 20 | import com.airsaid.localization.translate.AbstractTranslator; 21 | import com.airsaid.localization.translate.lang.Lang; 22 | import com.airsaid.localization.translate.lang.Languages; 23 | import com.airsaid.localization.translate.util.GsonUtil; 24 | import com.google.auto.service.AutoService; 25 | import com.intellij.openapi.diagnostic.Logger; 26 | import com.intellij.util.io.RequestBuilder; 27 | import icons.PluginIcons; 28 | import org.jetbrains.annotations.NotNull; 29 | import org.jetbrains.annotations.Nullable; 30 | 31 | import javax.swing.*; 32 | import java.util.List; 33 | 34 | 35 | @AutoService(AbstractTranslator.class) 36 | public class ChatGPTTranslator extends AbstractTranslator { 37 | 38 | private static final Logger LOG = Logger.getInstance(ChatGPTTranslator.class); 39 | private static final String KEY = "ChatGPT"; 40 | 41 | @Override 42 | public @NotNull String getKey() { 43 | return KEY; 44 | } 45 | 46 | @Override 47 | public @NotNull String getName() { 48 | return "OpenAI ChatGPT"; 49 | } 50 | 51 | @Override 52 | public @Nullable Icon getIcon() { 53 | return PluginIcons.OPENAI_ICON; 54 | } 55 | 56 | @Override 57 | public boolean isNeedAppId() { 58 | return false; 59 | } 60 | 61 | @Override 62 | public boolean isNeedAppKey() { 63 | return true; 64 | } 65 | 66 | @Override 67 | public @NotNull List getSupportedLanguages() { 68 | return Languages.getLanguages(); 69 | } 70 | 71 | @Override 72 | public String getAppKeyDisplay() { 73 | return "KEY"; 74 | } 75 | 76 | 77 | @Override 78 | public @NotNull String getRequestUrl(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 79 | return "https://api.openai.com/v1/chat/completions"; 80 | } 81 | 82 | @Override 83 | @NotNull 84 | public String getRequestBody(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 85 | String lang = toLang.getEnglishName(); 86 | String roleSystem = String.format("Translate the user provided text into high quality, well written %s. Apply these 4 translation rules; 1.Keep the exact original formatting and style, 2.Keep translations concise and just repeat the original text for unchanged translations (e.g. 'OK'), 3.Audience: native %s speakers, 4.Text can be used in Android app UI (limited space, concise translations!).", lang, lang); 87 | 88 | ChatGPTMessage role = new ChatGPTMessage("system", roleSystem); 89 | ChatGPTMessage msg = new ChatGPTMessage("user", String.format("Text to translate: %s", text)); 90 | 91 | OpenAIRequest body = new OpenAIRequest("gpt-3.5-turbo", List.of(role, msg)); 92 | 93 | return GsonUtil.getInstance().getGson().toJson(body); 94 | } 95 | 96 | @Override 97 | public void configureRequestBuilder(@NotNull RequestBuilder requestBuilder) { 98 | requestBuilder.tuner(connection -> { 99 | connection.setRequestProperty("Authorization", "Bearer " + getAppKey()); 100 | connection.setRequestProperty("Content-Type", "application/json"); 101 | }); 102 | } 103 | 104 | @Override 105 | public @NotNull String parsingResult(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text, @NotNull String resultText) { 106 | LOG.info("parsingResult ChatGPT: " + resultText); 107 | return GsonUtil.getInstance().getGson().fromJson(resultText, OpenAIResponse.class).getTranslation(); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/openai/OpenAIRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.openai; 19 | 20 | import java.util.List; 21 | 22 | public class OpenAIRequest { 23 | private String model; 24 | private List messages; 25 | 26 | public OpenAIRequest(String model, List messages) { 27 | this.model = model; 28 | this.messages = messages; 29 | } 30 | 31 | public String getModel() { 32 | return model; 33 | } 34 | 35 | public void setModel(String model) { 36 | this.model = model; 37 | } 38 | 39 | public List getMessages() { 40 | return messages; 41 | } 42 | 43 | public void setMessages(List messages) { 44 | this.messages = messages; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/openai/OpenAIResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.openai; 19 | 20 | import java.util.List; 21 | 22 | public class OpenAIResponse { 23 | private List choices; 24 | private Integer created; 25 | private String id; 26 | private String object; 27 | private Usage usage; 28 | 29 | public OpenAIResponse(List choices, Integer created, String id, String object, Usage usage) { 30 | this.choices = choices; 31 | this.created = created; 32 | this.id = id; 33 | this.object = object; 34 | this.usage = usage; 35 | } 36 | 37 | public List getChoices() { 38 | return choices; 39 | } 40 | 41 | public void setChoices(List choices) { 42 | this.choices = choices; 43 | } 44 | 45 | public Integer getCreated() { 46 | return created; 47 | } 48 | 49 | public void setCreated(Integer created) { 50 | this.created = created; 51 | } 52 | 53 | public String getId() { 54 | return id; 55 | } 56 | 57 | public void setId(String id) { 58 | this.id = id; 59 | } 60 | 61 | public String getObject() { 62 | return object; 63 | } 64 | 65 | public void setObject(String object) { 66 | this.object = object; 67 | } 68 | 69 | public Usage getUsage() { 70 | return usage; 71 | } 72 | 73 | public void setUsage(Usage usage) { 74 | this.usage = usage; 75 | } 76 | 77 | public String getTranslation() { 78 | if (choices != null && !choices.isEmpty()) { 79 | String result = choices.get(0).getMessage().getContent(); 80 | return result.trim(); 81 | 82 | } else { 83 | return ""; 84 | } 85 | } 86 | 87 | public static class Choice { 88 | private String finish_reason; 89 | private Integer index; 90 | private Message message; 91 | 92 | public Choice(String finish_reason, Integer index, Message message) { 93 | this.finish_reason = finish_reason; 94 | this.index = index; 95 | this.message = message; 96 | } 97 | 98 | public String getFinish_reason() { 99 | return finish_reason; 100 | } 101 | 102 | public void setFinish_reason(String finish_reason) { 103 | this.finish_reason = finish_reason; 104 | } 105 | 106 | public Integer getIndex() { 107 | return index; 108 | } 109 | 110 | public void setIndex(Integer index) { 111 | this.index = index; 112 | } 113 | 114 | public Message getMessage() { 115 | return message; 116 | } 117 | 118 | public void setMessage(Message message) { 119 | this.message = message; 120 | } 121 | } 122 | 123 | public static class Message { 124 | private String content; 125 | private String role; 126 | 127 | public Message(String content, String role) { 128 | this.content = content; 129 | this.role = role; 130 | } 131 | 132 | public String getContent() { 133 | return content; 134 | } 135 | 136 | public void setContent(String content) { 137 | this.content = content; 138 | } 139 | 140 | public String getRole() { 141 | return role; 142 | } 143 | 144 | public void setRole(String role) { 145 | this.role = role; 146 | } 147 | } 148 | 149 | public static class Usage { 150 | private Integer completion_tokens; 151 | private Integer prompt_tokens; 152 | private Integer total_tokens; 153 | 154 | public Usage(Integer completion_tokens, Integer prompt_tokens, Integer total_tokens) { 155 | this.completion_tokens = completion_tokens; 156 | this.prompt_tokens = prompt_tokens; 157 | this.total_tokens = total_tokens; 158 | } 159 | 160 | public Integer getCompletion_tokens() { 161 | return completion_tokens; 162 | } 163 | 164 | public void setCompletion_tokens(Integer completion_tokens) { 165 | this.completion_tokens = completion_tokens; 166 | } 167 | 168 | public Integer getPrompt_tokens() { 169 | return prompt_tokens; 170 | } 171 | 172 | public void setPrompt_tokens(Integer prompt_tokens) { 173 | this.prompt_tokens = prompt_tokens; 174 | } 175 | 176 | public Integer getTotal_tokens() { 177 | return total_tokens; 178 | } 179 | 180 | public void setTotal_tokens(Integer total_tokens) { 181 | this.total_tokens = total_tokens; 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/impl/youdao/YoudaoTranslationResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.impl.youdao; 19 | 20 | import com.airsaid.localization.translate.TranslationResult; 21 | import com.intellij.openapi.util.text.StringUtil; 22 | import org.jetbrains.annotations.NotNull; 23 | 24 | import java.util.List; 25 | import java.util.Objects; 26 | 27 | /** 28 | * @author airsaid 29 | */ 30 | public class YoudaoTranslationResult implements TranslationResult { 31 | private String requestId; 32 | private String errorCode; 33 | private List translation; 34 | 35 | public String getRequestId() { 36 | return requestId; 37 | } 38 | 39 | public void setRequestId(String requestId) { 40 | this.requestId = requestId; 41 | } 42 | 43 | public String getErrorCode() { 44 | return errorCode; 45 | } 46 | 47 | public void setErrorCode(String errorCode) { 48 | this.errorCode = errorCode; 49 | } 50 | 51 | public List getTranslation() { 52 | return translation; 53 | } 54 | 55 | public void setTranslation(List translation) { 56 | this.translation = translation; 57 | } 58 | 59 | @Override 60 | public boolean equals(Object o) { 61 | if (this == o) return true; 62 | if (o == null || getClass() != o.getClass()) return false; 63 | YoudaoTranslationResult that = (YoudaoTranslationResult) o; 64 | return Objects.equals(requestId, that.requestId); 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | return Objects.hash(requestId); 70 | } 71 | 72 | public boolean isSuccess() { 73 | String errorCode = getErrorCode(); 74 | return !StringUtil.isEmpty(errorCode) && "0".equals(errorCode); 75 | } 76 | 77 | @Override 78 | public @NotNull String getTranslationResult() { 79 | List translation = getTranslation(); 80 | if (translation != null) { 81 | String result = translation.get(0); 82 | return result != null ? result : ""; 83 | } 84 | return ""; 85 | } 86 | 87 | @Override 88 | public String toString() { 89 | return "YoudaoTranslationResult{" + 90 | "requestId='" + requestId + '\'' + 91 | ", errorCode='" + errorCode + '\'' + 92 | ", translation=" + translation + 93 | '}'; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/interceptors/EscapeCharactersInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.interceptors; 19 | 20 | import com.airsaid.localization.translate.services.TranslatorService; 21 | import com.intellij.openapi.util.text.StringUtil; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | /** 27 | * @author airsaid 28 | */ 29 | public class EscapeCharactersInterceptor implements TranslatorService.TranslationInterceptor { 30 | 31 | private final List needEscapeChars = new ArrayList<>(); 32 | 33 | public EscapeCharactersInterceptor() { 34 | needEscapeChars.add('@'); 35 | needEscapeChars.add('?'); 36 | needEscapeChars.add('\''); 37 | needEscapeChars.add('\"'); 38 | } 39 | 40 | @Override 41 | public String process(String text) { 42 | if (StringUtil.isEmpty(text)) { 43 | return text; 44 | } 45 | final StringBuilder result = new StringBuilder(); 46 | final char[] chars = text.toCharArray(); 47 | for (char ch : chars) { 48 | if (needEscapeChars.contains(ch)) { 49 | result.append('\\'); 50 | } 51 | result.append(ch); 52 | } 53 | return result.toString(); 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/lang/Lang.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.lang; 19 | 20 | import com.intellij.openapi.util.text.StringUtil; 21 | 22 | import java.util.Objects; 23 | 24 | /** 25 | * Language data class, which is an immutable class, 26 | * any modification to it will generate you a new object. 27 | * 28 | * @author airsaid 29 | */ 30 | public final class Lang implements Cloneable { 31 | private final int id; 32 | private final String code; 33 | private final String name; 34 | private final String englishName; 35 | private String translationCode; 36 | 37 | public Lang(int id, String code, String name, String englishName) { 38 | this.id = id; 39 | this.code = code; 40 | this.name = name; 41 | this.englishName = englishName; 42 | } 43 | 44 | public int getId() { 45 | return id; 46 | } 47 | 48 | public String getCode() { 49 | return code; 50 | } 51 | 52 | public String getName() { 53 | return name; 54 | } 55 | 56 | public String getEnglishName() { 57 | return englishName; 58 | } 59 | 60 | public Lang setTranslationCode(String translationCode) { 61 | final Lang newLang = this.clone(); 62 | Objects.requireNonNull(newLang).translationCode = translationCode; 63 | return newLang; 64 | } 65 | 66 | public String getTranslationCode() { 67 | if (!StringUtil.isEmpty(translationCode)) { 68 | return translationCode; 69 | } 70 | return code; 71 | } 72 | 73 | @Override 74 | public boolean equals(Object o) { 75 | if (this == o) return true; 76 | if (o == null || getClass() != o.getClass()) return false; 77 | Lang language = (Lang) o; 78 | return id == language.id; 79 | } 80 | 81 | @Override 82 | public int hashCode() { 83 | return Objects.hash(id); 84 | } 85 | 86 | @Override 87 | public Lang clone() { 88 | try { 89 | return (Lang) super.clone(); 90 | } catch (CloneNotSupportedException e) { 91 | e.printStackTrace(); 92 | } 93 | return null; 94 | } 95 | 96 | @Override 97 | public String toString() { 98 | return "Lang{" + 99 | "id=" + id + 100 | ", code='" + code + '\'' + 101 | ", name='" + name + '\'' + 102 | ", englishName='" + englishName + '\'' + 103 | ", translationCode='" + translationCode + '\'' + 104 | '}'; 105 | } 106 | } -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/services/TranslationCacheService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.services; 19 | 20 | import com.airsaid.localization.translate.util.GsonUtil; 21 | import com.airsaid.localization.translate.util.LRUCache; 22 | import com.google.gson.reflect.TypeToken; 23 | import com.intellij.openapi.Disposable; 24 | import com.intellij.openapi.components.*; 25 | import com.intellij.util.xmlb.Converter; 26 | import com.intellij.util.xmlb.XmlSerializerUtil; 27 | import com.intellij.util.xmlb.annotations.OptionTag; 28 | import com.intellij.util.xmlb.annotations.Transient; 29 | import org.jetbrains.annotations.NotNull; 30 | import org.jetbrains.annotations.Nullable; 31 | 32 | import java.lang.reflect.Type; 33 | import java.util.LinkedHashMap; 34 | import java.util.Map; 35 | 36 | /** 37 | * Cache the translated text to local disk. 38 | *

39 | * The maximum number of caches is set by the {@link #setMaxCacheSize(int)} method, 40 | * if exceed this size, remove old data through the LRU algorithm. 41 | * 42 | * @author airsaid 43 | */ 44 | @State( 45 | name = "com.airsaid.localization.translate.services.TranslationCacheService", 46 | storages = {@Storage("androidLocalizeTranslationCaches.xml")} 47 | ) 48 | @Service 49 | public final class TranslationCacheService implements PersistentStateComponent, Disposable { 50 | 51 | @Transient 52 | private static final int CACHE_MAX_SIZE = 500; 53 | 54 | @OptionTag(converter = LruCacheConverter.class) 55 | private final LRUCache lruCache = new LRUCache<>(CACHE_MAX_SIZE); 56 | 57 | public static TranslationCacheService getInstance() { 58 | return ServiceManager.getService(TranslationCacheService.class); 59 | } 60 | 61 | public void put(@NotNull String key, @NotNull String value) { 62 | lruCache.put(key, value); 63 | } 64 | 65 | @NotNull 66 | public String get(String key) { 67 | String value = lruCache.get(key); 68 | return value != null ? value : ""; 69 | } 70 | 71 | public void setMaxCacheSize(int maxCacheSize) { 72 | lruCache.setMaxCapacity(maxCacheSize); 73 | } 74 | 75 | @Override 76 | public @NotNull TranslationCacheService getState() { 77 | return this; 78 | } 79 | 80 | @Override 81 | public void loadState(@NotNull TranslationCacheService state) { 82 | XmlSerializerUtil.copyBean(state, this); 83 | } 84 | 85 | @Override 86 | public void dispose() { 87 | lruCache.clear(); 88 | } 89 | 90 | static class LruCacheConverter extends Converter> { 91 | @Override 92 | public @Nullable LRUCache fromString(@NotNull String value) { 93 | Type type = new TypeToken>() {}.getType(); 94 | Map map = GsonUtil.getInstance().getGson().fromJson(value, type); 95 | LRUCache lruCache = new LRUCache<>(CACHE_MAX_SIZE); 96 | for (Map.Entry entry : map.entrySet()) { 97 | lruCache.put(entry.getKey(), entry.getValue()); 98 | } 99 | return lruCache; 100 | } 101 | 102 | @Override 103 | public @Nullable String toString(@NotNull LRUCache lruCache) { 104 | Map values = new LinkedHashMap<>(); 105 | lruCache.forEach(values::put); 106 | return GsonUtil.getInstance().getGson().toJson(values); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/services/TranslatorService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.services; 19 | 20 | import com.airsaid.localization.translate.AbstractTranslator; 21 | import com.airsaid.localization.translate.impl.google.GoogleTranslator; 22 | import com.airsaid.localization.translate.interceptors.EscapeCharactersInterceptor; 23 | import com.airsaid.localization.translate.lang.Lang; 24 | import com.intellij.openapi.application.ApplicationManager; 25 | import com.intellij.openapi.components.Service; 26 | import com.intellij.openapi.components.ServiceManager; 27 | import com.intellij.openapi.diagnostic.Logger; 28 | import org.apache.commons.lang.StringUtils; 29 | import org.jetbrains.annotations.NotNull; 30 | import org.jetbrains.annotations.Nullable; 31 | 32 | import java.util.*; 33 | import java.util.function.Consumer; 34 | 35 | /** 36 | * @author airsaid 37 | */ 38 | @Service 39 | public final class TranslatorService { 40 | 41 | private static final Logger LOG = Logger.getInstance(TranslatorService.class); 42 | 43 | private AbstractTranslator selectedTranslator; 44 | private final AbstractTranslator defaultTranslator; 45 | private final TranslationCacheService cacheService; 46 | private final Map translators; 47 | private final List translationInterceptors; 48 | private boolean isEnableCache = true; 49 | private int intervalTime; 50 | 51 | public interface TranslationInterceptor { 52 | String process(String text); 53 | } 54 | 55 | public TranslatorService() { 56 | translators = new LinkedHashMap<>(); 57 | ServiceLoader serviceLoader = ServiceLoader.load( 58 | AbstractTranslator.class, getClass().getClassLoader() 59 | ); 60 | for (AbstractTranslator translator : serviceLoader) { 61 | translators.put(translator.getKey(), translator); 62 | } 63 | defaultTranslator = translators.get(GoogleTranslator.KEY); 64 | 65 | cacheService = TranslationCacheService.getInstance(); 66 | 67 | translationInterceptors = new ArrayList<>(); 68 | translationInterceptors.add(new EscapeCharactersInterceptor()); 69 | } 70 | 71 | @NotNull 72 | public static TranslatorService getInstance() { 73 | return ServiceManager.getService(TranslatorService.class); 74 | } 75 | 76 | public AbstractTranslator getDefaultTranslator() { 77 | return defaultTranslator; 78 | } 79 | 80 | public Map getTranslators() { 81 | return translators; 82 | } 83 | 84 | public void setSelectedTranslator(@NotNull AbstractTranslator selectedTranslator) { 85 | if (this.selectedTranslator != selectedTranslator) { 86 | LOG.info(String.format("setTranslator: %s", selectedTranslator)); 87 | this.selectedTranslator = selectedTranslator; 88 | } 89 | } 90 | 91 | @Nullable 92 | public AbstractTranslator getSelectedTranslator() { 93 | return selectedTranslator; 94 | } 95 | 96 | public void doTranslateByAsync(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text, @NotNull Consumer consumer) { 97 | ApplicationManager.getApplication().executeOnPooledThread(() -> { 98 | final String translatedText = doTranslate(fromLang, toLang, text); 99 | ApplicationManager.getApplication().invokeLater(() -> 100 | consumer.accept(translatedText)); 101 | }); 102 | } 103 | 104 | public String doTranslate(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 105 | LOG.info(String.format("doTranslate fromLang: %s, toLang: %s, text: %s", fromLang, toLang, text)); 106 | 107 | if (isEnableCache) { 108 | String cacheResult = cacheService.get(getCacheKey(fromLang, toLang, text)); 109 | if (!cacheResult.isEmpty()) { 110 | LOG.info(String.format("doTranslate cache result: %s", cacheResult)); 111 | return cacheResult; 112 | } 113 | } 114 | 115 | // Arabic numbers skip translation 116 | if (StringUtils.isNumeric(text)) { 117 | return text; 118 | } 119 | 120 | String result = selectedTranslator.doTranslate(fromLang, toLang, text); 121 | LOG.info(String.format("doTranslate result: %s", result)); 122 | for (TranslationInterceptor interceptor : translationInterceptors) { 123 | result = interceptor.process(result); 124 | LOG.info(String.format("doTranslate interceptor process result: %s", result)); 125 | } 126 | cacheService.put(getCacheKey(fromLang, toLang, text), result); 127 | delay(intervalTime); 128 | return result; 129 | } 130 | 131 | public void setEnableCache(boolean isEnableCache) { 132 | this.isEnableCache = isEnableCache; 133 | } 134 | 135 | public boolean isEnableCache() { 136 | return isEnableCache; 137 | } 138 | 139 | public void setMaxCacheSize(int maxCacheSize) { 140 | cacheService.setMaxCacheSize(maxCacheSize); 141 | } 142 | 143 | public void setTranslationInterval(int intervalTime) { 144 | this.intervalTime = intervalTime; 145 | } 146 | 147 | private String getCacheKey(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { 148 | return fromLang.getCode() + "_" + toLang.getCode() + "_" + text; 149 | } 150 | 151 | private void delay(int second) { 152 | if (second <= 0) return; 153 | try { 154 | LOG.info(String.format("doTranslate delay time: %d second.", second)); 155 | Thread.sleep(second * 1000L); 156 | } catch (InterruptedException e) { 157 | e.printStackTrace(); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/util/AgentUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.util; 19 | 20 | import com.intellij.openapi.util.SystemInfo; 21 | 22 | import java.util.Arrays; 23 | import java.util.List; 24 | import java.util.stream.Collectors; 25 | 26 | /** 27 | * @author airsaid 28 | */ 29 | public class AgentUtil { 30 | 31 | private static final String CHROME_VERSION = "98.0.4758.102"; 32 | private static final String EDGE_VERSION = "98.0.1108.62"; 33 | 34 | private AgentUtil() { 35 | throw new AssertionError("No com.airsaid.localization.translate.util.AgentUtil instances for you!"); 36 | } 37 | 38 | public static String getUserAgent() { 39 | String arch = System.getProperty("os.arch"); 40 | boolean is64Bit = arch != null && arch.contains("64"); 41 | String systemInformation; 42 | if (SystemInfo.isWindows) { 43 | systemInformation = is64Bit ? "Windows NT " + SystemInfo.OS_VERSION + "; Win64; x64" : "Windows NT " + SystemInfo.OS_VERSION; 44 | } else if (SystemInfo.isMac) { 45 | List parts = Arrays.stream(SystemInfo.OS_VERSION.split("\\.")).collect(Collectors.toList()); 46 | if (parts.size() < 3) { 47 | parts.add("0"); 48 | } 49 | systemInformation = String.format("Macintosh; Intel Mac OS X %s", String.join("_", parts)); 50 | } else { 51 | systemInformation = is64Bit ? "X11; Linux x86_64" : "X11; Linux x86"; 52 | } 53 | return "Mozilla/5.0 (".concat(systemInformation).concat(") AppleWebKit/537.36 (KHTML, like Gecko) Chrome/") 54 | .concat(CHROME_VERSION).concat(" Safari/537.36 Edg/").concat(EDGE_VERSION); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/util/GsonUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.util; 19 | 20 | import com.google.gson.Gson; 21 | import com.google.gson.GsonBuilder; 22 | 23 | /** 24 | * @author airsaid 25 | */ 26 | public class GsonUtil { 27 | 28 | private final Gson gson; 29 | 30 | public GsonUtil() { 31 | gson = new GsonBuilder().create(); 32 | } 33 | 34 | public static GsonUtil getInstance() { 35 | return GsonUtilHolder.sInstance; 36 | } 37 | 38 | public Gson getGson() { 39 | return gson; 40 | } 41 | 42 | private static class GsonUtilHolder { 43 | private static final GsonUtil sInstance = new GsonUtil(); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/util/LRUCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.util; 19 | 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | import java.util.function.BiConsumer; 23 | 24 | /** 25 | * @author airsaid 26 | */ 27 | public class LRUCache { 28 | 29 | private final Map> caches; 30 | private Node head; 31 | private Node tail; 32 | 33 | private int maxCapacity; 34 | 35 | public LRUCache(int initialCapacity) { 36 | maxCapacity = initialCapacity; 37 | if (initialCapacity <= 0) { 38 | throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); 39 | } 40 | caches = new LinkedHashMap<>(initialCapacity); 41 | } 42 | 43 | public void put(K key, V value) { 44 | while (isFull()) { 45 | removeTailNode(); 46 | } 47 | Node newNode = new Node<>(key, value); 48 | caches.put(key, newNode); 49 | moveToHeadNode(newNode); 50 | } 51 | 52 | public V get(K key) { 53 | if (caches.containsKey(key)) { 54 | Node newHead = caches.get(key); 55 | moveToHeadNode(newHead); 56 | return newHead.value; 57 | } 58 | return null; 59 | } 60 | 61 | public int size() { 62 | return caches.size(); 63 | } 64 | 65 | public boolean isFull() { 66 | return size() > 0 && size() >= maxCapacity; 67 | } 68 | 69 | public boolean isEmpty() { 70 | return size() <= 0; 71 | } 72 | 73 | public void forEach(BiConsumer consumer) { 74 | for (Map.Entry> entry : caches.entrySet()) { 75 | K key = entry.getKey(); 76 | Node value = entry.getValue(); 77 | consumer.accept(key, value.value); 78 | } 79 | } 80 | 81 | public void clear() { 82 | caches.clear(); 83 | head = null; 84 | tail = null; 85 | } 86 | 87 | private void moveToHeadNode(Node node) { 88 | if (head == null) { 89 | head = node; 90 | tail = node; 91 | return; 92 | } 93 | 94 | node.next = head; 95 | head.prev = node; 96 | head = node; 97 | } 98 | 99 | private void removeTailNode() { 100 | if (tail == null) return; 101 | 102 | caches.remove(tail.key); 103 | Node prev = tail.prev; 104 | if (prev != null) { 105 | prev.next = null; 106 | tail.prev = null; 107 | } 108 | tail = prev; 109 | } 110 | 111 | public void setMaxCapacity(int maxCapacity) { 112 | this.maxCapacity = maxCapacity; 113 | } 114 | 115 | public int getMaxCapacity() { 116 | return maxCapacity; 117 | } 118 | 119 | private static class Node { 120 | public K key; 121 | public V value; 122 | public Node prev; 123 | public Node next; 124 | 125 | public Node(K key, V value) { 126 | this.key = key; 127 | this.value = value; 128 | } 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/util/MD5.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.util; 19 | 20 | import java.nio.charset.StandardCharsets; 21 | import java.security.MessageDigest; 22 | import java.security.NoSuchAlgorithmException; 23 | 24 | /** 25 | * @author airsaid 26 | */ 27 | public class MD5 { 28 | 29 | private static final char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 30 | 31 | private MD5() { 32 | throw new AssertionError("No com.airsaid.localization.translate.util.MD5 instances for you!"); 33 | } 34 | 35 | public static String md5(String input) { 36 | if (input == null) { 37 | return null; 38 | } 39 | 40 | try { 41 | MessageDigest messageDigest = MessageDigest.getInstance("MD5"); 42 | byte[] inputByteArray = input.getBytes(StandardCharsets.UTF_8); 43 | messageDigest.update(inputByteArray); 44 | byte[] resultByteArray = messageDigest.digest(); 45 | return byteArrayToHex(resultByteArray); 46 | } catch (NoSuchAlgorithmException e) { 47 | return null; 48 | } 49 | } 50 | 51 | private static String byteArrayToHex(byte[] byteArray) { 52 | char[] resultCharArray = new char[byteArray.length * 2]; 53 | int index = 0; 54 | for (byte b : byteArray) { 55 | resultCharArray[index++] = hexDigits[b >>> 4 & 0xf]; 56 | resultCharArray[index++] = hexDigits[b & 0xf]; 57 | } 58 | return new String(resultCharArray); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/translate/util/UrlBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.translate.util; 19 | 20 | import com.intellij.openapi.util.Pair; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Arrays; 24 | import java.util.List; 25 | import java.util.stream.Collectors; 26 | 27 | /** 28 | * @author airsaid 29 | */ 30 | public class UrlBuilder { 31 | 32 | private final String baseUrl; 33 | private final List> queryParameters; 34 | 35 | public UrlBuilder(String baseUrl) { 36 | this.baseUrl = baseUrl; 37 | queryParameters = new ArrayList<>(); 38 | } 39 | 40 | public UrlBuilder addQueryParameter(String key, String value) { 41 | queryParameters.add(Pair.create(key, value)); 42 | return this; 43 | } 44 | 45 | public UrlBuilder addQueryParameters(String key, String... values) { 46 | queryParameters.addAll(Arrays.stream(values).map(value -> Pair.create(key, value)).collect(Collectors.toList())); 47 | return this; 48 | } 49 | 50 | public String build() { 51 | StringBuilder result = new StringBuilder(baseUrl); 52 | for (int i = 0; i < queryParameters.size(); i++) { 53 | if (i == 0) { 54 | result.append("?"); 55 | } else { 56 | result.append("&"); 57 | } 58 | Pair param = queryParameters.get(i); 59 | String key = param.first; 60 | String value = param.second; 61 | result.append(key) 62 | .append("=") 63 | .append(value); 64 | } 65 | return result.toString(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/ui/FixedLinkLabel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.ui; 19 | 20 | import com.intellij.icons.AllIcons; 21 | import com.intellij.ui.components.labels.LinkLabel; 22 | 23 | import java.awt.event.MouseAdapter; 24 | import java.awt.event.MouseEvent; 25 | 26 | /** 27 | * Fixed the problem that sometimes click does not respond. 28 | * 29 | * @author airsaid 30 | */ 31 | public class FixedLinkLabel extends LinkLabel { 32 | private boolean isDoClick = false; 33 | 34 | public FixedLinkLabel() { 35 | super("", AllIcons.Ide.Link); 36 | addMouseListener(new MouseAdapter() { 37 | @Override 38 | public void mouseReleased(MouseEvent e) { 39 | if (isEnabled() && isInClickableArea(e.getPoint())) { 40 | doClick(); 41 | } 42 | } 43 | 44 | @Override 45 | public void mouseExited(MouseEvent e) { 46 | isDoClick = false; 47 | } 48 | }); 49 | } 50 | 51 | @Override 52 | public void doClick() { 53 | if (!isDoClick) { 54 | isDoClick = true; 55 | super.doClick(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/ui/SelectLanguagesDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/ui/SupportLanguagesDialog.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.ui; 19 | 20 | import com.airsaid.localization.translate.AbstractTranslator; 21 | import com.airsaid.localization.translate.lang.Lang; 22 | import com.intellij.openapi.ui.DialogWrapper; 23 | import com.intellij.ui.components.JBLabel; 24 | import org.jetbrains.annotations.NotNull; 25 | import org.jetbrains.annotations.Nullable; 26 | 27 | import javax.swing.*; 28 | import java.awt.*; 29 | import java.util.Comparator; 30 | import java.util.List; 31 | 32 | /** 33 | * @author airsaid 34 | */ 35 | public class SupportLanguagesDialog extends DialogWrapper { 36 | 37 | private final AbstractTranslator mTranslator; 38 | 39 | public SupportLanguagesDialog(AbstractTranslator translator) { 40 | super(true); 41 | setTitle(translator.getName() + " Translator Supported Languages"); 42 | mTranslator = translator; 43 | init(); 44 | } 45 | 46 | @Override 47 | protected @Nullable JComponent createCenterPanel() { 48 | List supportedLanguages = mTranslator.getSupportedLanguages(); 49 | supportedLanguages.sort(new EnglishNameComparator()); 50 | JPanel contentPanel = new JPanel(new GridLayout(supportedLanguages.size() / 4, 4, 10, 20)); 51 | for (Lang supportedLanguage : supportedLanguages) { 52 | contentPanel.add(new JBLabel(supportedLanguage.getEnglishName())); 53 | } 54 | return contentPanel; 55 | } 56 | 57 | @Override 58 | protected @Nullable String getDimensionServiceKey() { 59 | String key = mTranslator.getKey(); 60 | return "#com.airsaid.localization.ui.SupportLanguagesDialog#".concat(key); 61 | } 62 | 63 | @Override 64 | protected Action @NotNull [] createActions() { 65 | return new Action[]{}; 66 | } 67 | 68 | static class EnglishNameComparator implements Comparator { 69 | @Override 70 | public int compare(Lang o1, Lang o2) { 71 | return o1.getEnglishName().compareTo(o2.getEnglishName()); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/utils/LanguageUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.utils; 19 | 20 | import com.airsaid.localization.constant.Constants; 21 | import com.airsaid.localization.translate.lang.Lang; 22 | import com.intellij.ide.util.PropertiesComponent; 23 | import com.intellij.openapi.project.Project; 24 | import org.apache.http.util.TextUtils; 25 | import org.jetbrains.annotations.NotNull; 26 | import org.jetbrains.annotations.Nullable; 27 | 28 | import java.util.Arrays; 29 | import java.util.List; 30 | import java.util.Objects; 31 | 32 | /** 33 | * A util class that operates on language data. 34 | * 35 | * @author airsaid 36 | */ 37 | public class LanguageUtil { 38 | 39 | private static final String SEPARATOR_SELECTED_LANGUAGES_CODE = ","; 40 | 41 | private LanguageUtil() { 42 | throw new AssertionError("No com.airsaid.localization.utils.LanguageUtil instances for you!"); 43 | } 44 | 45 | /** 46 | * Save the language data selected in the current project. 47 | * 48 | * @param project current project. 49 | * @param languages selected language. 50 | */ 51 | public static void saveSelectedLanguage(@NotNull Project project, @NotNull List languages) { 52 | Objects.requireNonNull(project); 53 | Objects.requireNonNull(languages); 54 | 55 | PropertiesComponent.getInstance(project) 56 | .setValue(Constants.KEY_SELECTED_LANGUAGES, getLanguageIdString(languages)); 57 | } 58 | 59 | /** 60 | * Get saved language code data in the current project. 61 | * 62 | * @param project current project. 63 | * @return null if not saved, otherwise return the saved language id data. 64 | */ 65 | @Nullable 66 | public static List getSelectedLanguageIds(@NotNull Project project) { 67 | Objects.requireNonNull(project); 68 | 69 | String codeString = PropertiesComponent.getInstance(project) 70 | .getValue(Constants.KEY_SELECTED_LANGUAGES); 71 | 72 | if (TextUtils.isEmpty(codeString)) { 73 | return null; 74 | } 75 | 76 | return Arrays.asList(codeString.split(SEPARATOR_SELECTED_LANGUAGES_CODE)); 77 | } 78 | 79 | @NotNull 80 | private static String getLanguageIdString(@NotNull List language) { 81 | StringBuilder codes = new StringBuilder(language.size()); 82 | for (int i = 0, len = language.size(); i < len; i++) { 83 | Lang lang = language.get(i); 84 | codes.append(lang.getId()); 85 | if (i < len - 1) { 86 | codes.append(SEPARATOR_SELECTED_LANGUAGES_CODE); 87 | } 88 | } 89 | return codes.toString(); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/utils/NotificationUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.utils; 19 | 20 | import com.intellij.notification.NotificationGroup; 21 | import com.intellij.notification.NotificationGroupManager; 22 | import com.intellij.notification.NotificationType; 23 | import com.intellij.openapi.project.Project; 24 | import org.jetbrains.annotations.Nullable; 25 | 26 | /** 27 | * @author airsaid 28 | */ 29 | public class NotificationUtil { 30 | 31 | private static final String NOTIFICATION_GROUP_ID = "Android Localize Plugin"; 32 | 33 | private static final NotificationGroup NOTIFICATION_GROUP = 34 | NotificationGroupManager.getInstance().getNotificationGroup(NOTIFICATION_GROUP_ID); 35 | 36 | private NotificationUtil() { 37 | throw new AssertionError("No com.airsaid.localization.utils.NotificationUtil instances for you!"); 38 | } 39 | 40 | public static void notifyInfo(@Nullable Project project, String content) { 41 | NOTIFICATION_GROUP.createNotification(content, NotificationType.INFORMATION) 42 | .notify(project); 43 | } 44 | 45 | public static void notifyWarning(@Nullable Project project, String content) { 46 | NOTIFICATION_GROUP.createNotification(content, NotificationType.WARNING) 47 | .notify(project); 48 | } 49 | 50 | public static void notifyError(@Nullable Project project, String content) { 51 | NOTIFICATION_GROUP.createNotification(content, NotificationType.ERROR) 52 | .notify(project); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/utils/SecureStorage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.utils; 19 | 20 | import com.airsaid.localization.constant.Constants; 21 | import com.intellij.credentialStore.CredentialAttributes; 22 | import com.intellij.credentialStore.CredentialAttributesKt; 23 | import com.intellij.credentialStore.Credentials; 24 | import com.intellij.ide.passwordSafe.PasswordSafe; 25 | import org.jetbrains.annotations.NotNull; 26 | 27 | /** 28 | * @author airsaid 29 | */ 30 | public class SecureStorage { 31 | 32 | private final String key; 33 | 34 | public SecureStorage(@NotNull String key) { 35 | this.key = key; 36 | } 37 | 38 | public void save(@NotNull String text) { 39 | CredentialAttributes credentialAttributes = createCredentialAttributes(); 40 | Credentials credentials = new Credentials(key, text); 41 | PasswordSafe.getInstance().set(credentialAttributes, credentials); 42 | } 43 | 44 | @NotNull 45 | public String read() { 46 | String password = PasswordSafe.getInstance().getPassword(createCredentialAttributes()); 47 | return password != null ? password : ""; 48 | } 49 | 50 | @NotNull 51 | private CredentialAttributes createCredentialAttributes() { 52 | return new CredentialAttributes(CredentialAttributesKt.generateServiceName(Constants.PLUGIN_NAME, key)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/airsaid/localization/utils/TextUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.airsaid.localization.utils; 19 | 20 | import com.intellij.openapi.util.text.StringUtil; 21 | import org.jetbrains.annotations.Nullable; 22 | 23 | /** 24 | * @author airsaid 25 | */ 26 | public class TextUtil { 27 | 28 | public static boolean isEmptyOrSpacesLineBreak(@Nullable CharSequence s) { 29 | if (StringUtil.isEmpty(s)) { 30 | return true; 31 | } 32 | for (int i = 0; i < s.length(); i++) { 33 | if (s.charAt(i) != ' ' && s.charAt(i) != '\r' && s.charAt(i) != '\n') { 34 | return false; 35 | } 36 | } 37 | return true; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/icons/PluginIcons.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Airsaid. https://github.com/airsaid 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package icons; 18 | 19 | import com.intellij.openapi.util.IconLoader; 20 | 21 | import javax.swing.*; 22 | 23 | /** 24 | * @author airsaid 25 | */ 26 | public interface PluginIcons { 27 | Icon TRANSLATE_ACTION_ICON = load("/icons/icon_translate.svg"); 28 | Icon GOOGLE_ICON = load("/icons/icon_google.svg"); 29 | Icon BAIDU_ICON = load("/icons/icon_baidu.svg"); 30 | Icon YOUDAO_ICON = load("/icons/icon_youdao.svg"); 31 | Icon MICROSOFT_ICON = load("/icons/icon_microsoft.svg"); 32 | Icon ALI_ICON = load("/icons/icon_ali.svg"); 33 | Icon DEEP_L_ICON = load("/icons/icon_deepl.svg"); 34 | Icon OPENAI_ICON = load("/icons/icon_openai.svg"); 35 | 36 | private static Icon load(String path) { 37 | return IconLoader.getIcon(path, PluginIcons.class); 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.github.airsaid.androidlocalize 3 | AndroidLocalize 4 | Airsaid 5 | 6 | com.intellij.modules.platform 7 | 8 | 9 | 12 | 13 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/icons/icon_ali.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /src/main/resources/icons/icon_baidu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/icons/icon_baidu_dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/icons/icon_deepl.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 9 | 11 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/resources/icons/icon_google.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/icons/icon_openai.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/icons/icon_translate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/icons/icon_youdao.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/java/com/airsaid/localization/translate/impl/google/GoogleTokenTest.java: -------------------------------------------------------------------------------- 1 | package com.airsaid.localization.translate.impl.google; 2 | 3 | import com.intellij.openapi.util.Pair; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | 7 | /** 8 | * @author airsaid 9 | */ 10 | public class GoogleTokenTest { 11 | 12 | @Test 13 | public void getToken() { 14 | long a = 202905874L; 15 | long b = 544157181L; 16 | long c = 419689L; 17 | Pair tkk = Pair.create(c, a + b); 18 | Assertions.assertEquals("34939.454418", GoogleToken.getToken("Translate", tkk)); 19 | Assertions.assertEquals("671407.809414", GoogleToken.getToken("Google translate", tkk)); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/test/java/com/airsaid/localization/translate/util/LRUCacheTest.java: -------------------------------------------------------------------------------- 1 | package com.airsaid.localization.translate.util; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | /** 8 | * @author airsaid 9 | */ 10 | class LRUCacheTest { 11 | @Test 12 | void testEmpty() { 13 | LRUCache lruCache = new LRUCache<>(10); 14 | assertTrue(lruCache.isEmpty()); 15 | assertFalse(lruCache.isFull()); 16 | assertNull(lruCache.get("key")); 17 | } 18 | 19 | @Test 20 | void testFull() { 21 | LRUCache lruCache = new LRUCache<>(1); 22 | lruCache.put("key", "value"); 23 | assertFalse(lruCache.isEmpty()); 24 | assertTrue(lruCache.isFull()); 25 | assertNotNull(lruCache.get("key")); 26 | } 27 | 28 | @Test 29 | void testPut() { 30 | LRUCache lruCache = new LRUCache<>(3); 31 | lruCache.put("key1", "value1"); 32 | lruCache.put("key2", "value2"); 33 | lruCache.put("key3", "value3"); 34 | lruCache.put("key4", "value4"); 35 | assertNull(lruCache.get("key1")); 36 | assertEquals("value2", lruCache.get("key2")); 37 | assertEquals("value3", lruCache.get("key3")); 38 | assertEquals("value4", lruCache.get("key4")); 39 | lruCache.put("key5", "value5"); 40 | assertNull(lruCache.get("key2")); 41 | } 42 | } -------------------------------------------------------------------------------- /src/test/java/com/airsaid/localization/translate/util/UrlBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.airsaid.localization.translate.util; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | /** 7 | * @author airsaid 8 | */ 9 | public class UrlBuilderTest { 10 | @Test 11 | public void testNoParameterBuild() { 12 | String result = new UrlBuilder("https://translate.googleapis.com/translate_a/single") 13 | .build(); 14 | Assertions.assertEquals("https://translate.googleapis.com/translate_a/single", result); 15 | } 16 | 17 | @Test 18 | public void testSingleParameterBuild() { 19 | String result = new UrlBuilder("https://translate.googleapis.com/translate_a/single") 20 | .addQueryParameter("sl", "en") 21 | .build(); 22 | Assertions.assertEquals("https://translate.googleapis.com/translate_a/single?sl=en", result); 23 | } 24 | 25 | @Test 26 | public void testSomeParameterBuild() { 27 | String result = new UrlBuilder("https://translate.googleapis.com/translate_a/single") 28 | .addQueryParameter("sl", "en") 29 | .addQueryParameter("tl", "zh-CN") 30 | .addQueryParameter("client", "gtx") 31 | .addQueryParameter("dt", "t") 32 | .build(); 33 | Assertions.assertEquals("https://translate.googleapis.com/translate_a/single?sl=en&tl=zh-CN&client=gtx&dt=t", result); 34 | } 35 | 36 | @Test 37 | public void testRepeatParameterBuild() { 38 | String result = new UrlBuilder("https://translate.googleapis.com/translate_a/single") 39 | .addQueryParameter("sl", "en") 40 | .addQueryParameter("tl", "zh-CN") 41 | .addQueryParameters("dt", "t", "bd", "ex") 42 | .build(); 43 | Assertions.assertEquals("https://translate.googleapis.com/translate_a/single?sl=en&tl=zh-CN&dt=t&dt=bd&dt=ex", result); 44 | } 45 | } -------------------------------------------------------------------------------- /src/test/java/com/airsaid/localization/utils/TextUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.airsaid.localization.utils; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | /** 8 | * @author airsaid 9 | */ 10 | class TextUtilTest { 11 | 12 | @Test 13 | void isEmptyOrSpacesLineBreak() { 14 | assertTrue(TextUtil.isEmptyOrSpacesLineBreak(null)); 15 | assertTrue(TextUtil.isEmptyOrSpacesLineBreak("")); 16 | assertTrue(TextUtil.isEmptyOrSpacesLineBreak(" ")); 17 | assertTrue(TextUtil.isEmptyOrSpacesLineBreak(" ")); 18 | assertTrue(TextUtil.isEmptyOrSpacesLineBreak("\r")); 19 | assertTrue(TextUtil.isEmptyOrSpacesLineBreak("\n")); 20 | assertTrue(TextUtil.isEmptyOrSpacesLineBreak("\r\n")); 21 | assertTrue(TextUtil.isEmptyOrSpacesLineBreak(" \r\n ")); 22 | assertTrue(TextUtil.isEmptyOrSpacesLineBreak(" \r \n ")); 23 | 24 | assertFalse(TextUtil.isEmptyOrSpacesLineBreak("text")); 25 | assertFalse(TextUtil.isEmptyOrSpacesLineBreak("text ")); 26 | assertFalse(TextUtil.isEmptyOrSpacesLineBreak(" text")); 27 | assertFalse(TextUtil.isEmptyOrSpacesLineBreak(" text ")); 28 | assertFalse(TextUtil.isEmptyOrSpacesLineBreak("\ntext")); 29 | assertFalse(TextUtil.isEmptyOrSpacesLineBreak("text\n")); 30 | assertFalse(TextUtil.isEmptyOrSpacesLineBreak("\rtext")); 31 | assertFalse(TextUtil.isEmptyOrSpacesLineBreak("text\r")); 32 | assertFalse(TextUtil.isEmptyOrSpacesLineBreak("\r\ntext\r\n")); 33 | } 34 | } --------------------------------------------------------------------------------