diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2315293c8..833939a95 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,19 +1,19 @@ -# GitHub Actions Workflow created for testing and preparing the plugin release in the following steps: -# - validate Gradle Wrapper, -# - run test and verifyPlugin tasks, -# - run buildPlugin task and prepare artifact for the further tests, -# - run IntelliJ Plugin Verifier, -# - create a draft release. +# GitHub Actions Workflow is created for testing and preparing the plugin release in the following steps: +# - Validate Gradle Wrapper. +# - Run 'test' and 'verifyPlugin' tasks. +# - Run Qodana inspections. +# - Run the 'buildPlugin' task and prepare artifact for further tests. +# - Run the 'runPluginVerifier' task. +# - Create a draft release. # -# Workflow is triggered on push and pull_request events. +# The workflow is triggered on push and pull_request events. # -# Docs: -# - GitHub Actions: https://help.github.com/en/actions -# - IntelliJ Plugin Verifier GitHub Action: https://github.com/ChrisCarini/intellij-platform-plugin-verifier-action +# GitHub Actions reference: https://help.github.com/en/actions +## name: Build on: - # Trigger the workflow on pushes to only the 'master' branch (this avoids duplicate checks being run e.g. for dependabot pull requests) + # Trigger the workflow on pushes to only the 'main' branch (this avoids duplicate checks being run e.g., for dependabot pull requests) push: branches: - master @@ -38,6 +38,10 @@ on: - 'README.md' - 'FINANCIAL_CONTRIBUTORS.md' +concurrency: + group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" + cancel-in-progress: true + jobs: # Run Gradle Wrapper Validation Action to verify the wrapper's checksum @@ -49,30 +53,29 @@ jobs: outputs: version: ${{ steps.properties.outputs.version }} changelog: ${{ steps.properties.outputs.changelog }} + pluginVerifierHomeDir: ${{ steps.properties.outputs.pluginVerifierHomeDir }} steps: - # Free GitHub Actions Environment Disk Space - - name: Maximize Build Space - uses: jlumbroso/free-disk-space@main - with: - tool-cache: false - large-packages: false - - # Check out current repository + # Check out the current repository - name: Fetch Sources uses: actions/checkout@v4 # Validate wrapper - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v1.1.0 + uses: gradle/wrapper-validation-action@v2 - # Setup Java 11 environment for the next steps + # Set up Java environment for the next steps - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: zulu java-version: 11 - cache: gradle + + # Setup Gradle + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + with: + gradle-home-cache-cleanup: true # Set environment variables - name: Export Properties @@ -98,6 +101,51 @@ jobs: ./gradlew listProductsReleases # prepare list of IDEs for Plugin Verifier + # Build plugin + - name: Build plugin + run: ./gradlew buildPlugin + + # Prepare plugin archive content for creating artifact + - name: Prepare Plugin Artifact + id: artifact + shell: bash + run: | + cd ${{ github.workspace }}/build/distributions + FILENAME=`ls *.zip` + unzip "$FILENAME" -d content + echo "filename=${FILENAME:0:-4}" >> $GITHUB_OUTPUT + + # Store already-built plugin as an artifact for downloading + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.artifact.outputs.filename }} + path: ./build/distributions/content/*/* + + # Run tests and upload a code coverage report + test: + name: Test + needs: [ build ] + runs-on: ubuntu-latest + steps: + + # Check out the current repository + - name: Fetch Sources + uses: actions/checkout@v4 + + # Set up Java environment for the next steps + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 11 + + # Setup Gradle + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + with: + gradle-home-cache-cleanup: true + # Run tests - name: Run Tests run: ./gradlew check @@ -105,76 +153,88 @@ jobs: # Collect Tests Result of failed tests - name: Collect Tests Result if: ${{ failure() }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: tests-report path: ${{ github.workspace }}/build/reports/tests - # Upload the Kover report to CodeCov - - name: Upload Code Coverage Report - uses: codecov/codecov-action@v3 - with: - files: ${{ github.workspace }}/build/reports/kover/report.xml + ## Upload the Kover report to CodeCov + #- name: Upload Code Coverage Report + # uses: codecov/codecov-action@v4 + # with: + # files: ${{ github.workspace }}/build/reports/kover/report.xml # Collect Code Coverage Report - name: Collect Code Coverage Report if: ${{ always() }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: code-coverage-report path: ${{ github.workspace }}/build/reports/kover/html + # Run plugin structure verification along with IntelliJ Plugin Verifier + verify: + name: Verify plugin + needs: [ build ] + runs-on: ubuntu-latest + steps: + + # Free GitHub Actions Environment Disk Space + - name: Maximize Build Space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: false + large-packages: false + + # Check out the current repository + - name: Fetch Sources + uses: actions/checkout@v4 + + # Set up Java environment for the next steps + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 11 + + # Setup Gradle + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + with: + gradle-home-cache-cleanup: true + # Cache Plugin Verifier IDEs - name: Setup Plugin Verifier IDEs Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: - path: ${{ steps.properties.outputs.pluginVerifierHomeDir }}/ides + path: ${{ needs.build.outputs.pluginVerifierHomeDir }}/ides key: plugin-verifier-${{ hashFiles('build/listProductsReleases.txt') }} # Run Verify Plugin task and IntelliJ Plugin Verifier tool - name: Run Plugin Verification tasks continue-on-error: true - run: ./gradlew runPluginVerifier -Pplugin.verifier.home.dir=${{ steps.properties.outputs.pluginVerifierHomeDir }} + run: ./gradlew runPluginVerifier -Dplugin.verifier.home.dir=${{ needs.build.outputs.pluginVerifierHomeDir }} - # Collect Plugin Verifier Report - - name: Collect Plugin Verifier Report + # Collect Plugin Verifier Result + - name: Collect Plugin Verifier Result if: ${{ always() }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: plugin-verifier-report path: ${{ github.workspace }}/build/reports/pluginVerifier - # Run Qodana inspections - #- name: Qodana - Code Inspection - # uses: JetBrains/qodana-action@v4.2.5 - - # Prepare plugin archive content for creating artifact - - name: Prepare Plugin Artifact - id: artifact - shell: bash - run: | - cd ${{ github.workspace }}/build/distributions - FILENAME=`ls *.zip` - unzip "$FILENAME" -d content - echo "filename=${FILENAME:0:-4}" >> $GITHUB_OUTPUT - - # Store already-built plugin as an artifact for downloading - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: ${{ steps.artifact.outputs.filename }} - path: ./build/distributions/content/*/* - # Prepare a draft release for GitHub Releases page for the manual verification # If accepted and published, release workflow would be triggered releaseDraft: name: Release Draft if: github.event_name != 'pull_request' - needs: build + needs: [ build, test, verify ] runs-on: ubuntu-latest + permissions: + contents: write steps: - # Check out current repository + # Check out the current repository - name: Fetch Sources uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 952599bca..2e6bb3d1c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,6 @@ -# GitHub Actions Workflow created for handling the release process based on the draft release prepared -# with the Build workflow. Running the publishPlugin task requires the PUBLISH_TOKEN secret provided. +# GitHub Actions Workflow created for handling the release process based on the draft release prepared with the Build workflow. +# Running the publishPlugin task requires all-following secrets to be provided: PUBLISH_TOKEN, PRIVATE_KEY, PRIVATE_KEY_PASSWORD, CERTIFICATE_CHAIN. +# See https://plugins.jetbrains.com/docs/intellij/plugin-signing.html for more information. name: Release @@ -16,21 +17,29 @@ jobs: release: name: Publish Plugin runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write steps: - # Check out current repository + # Check out the current repository - name: Fetch Sources uses: actions/checkout@v4 with: ref: ${{ github.event.release.tag_name }} - # Setup Java 11 environment for the next steps + # Set up Java environment for the next steps - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: zulu java-version: 11 - cache: gradle + + # Setup Gradle + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + with: + gradle-home-cache-cleanup: true # Set environment variables - name: Export Properties @@ -60,19 +69,21 @@ jobs: echo "$CHANGELOG" >> $GITHUB_OUTPUT echo 'EOF' >> $GITHUB_OUTPUT - # Update Unreleased section with the current release note + # Update the unreleased section with the current release note - name: Patch Changelog if: steps.properties.outputs.pluginBuildMetadata == '' && steps.properties.outputs.changelog != '' + env: + CHANGELOG: ${{ steps.properties.outputs.changelog }} run: | - ./gradlew patchChangelog --release-note="$(cat << 'EOM' - ${{ steps.properties.outputs.changelog }} - EOM - )" + ./gradlew patchChangelog --release-note="$CHANGELOG" # Publish the plugin to the Marketplace - name: Publish Plugin env: PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} + CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} + PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} + PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} run: ./gradlew publishPlugin # Upload artifact as a release asset diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index e29f1e7cc..71c8ab0cd 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -26,7 +26,7 @@ jobs: # Setup Java 11 environment for the next steps - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: zulu java-version: 11 @@ -63,7 +63,7 @@ jobs: # Store already-built plugin as an artifact for downloading - name: Upload artifact if: steps.properties.outputs.publishChannel == 'snapshot' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ steps.artifact.outputs.filename }} path: ./build/distributions/content/*/* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ee3f3512..4af598e85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +- Bug fixes. +- Bug 修复 + ## [3.5.6] (2023/11/19) - Fix the problem of missing required parameters for document translation in the Youdao translation engine. diff --git a/DESCRIPTION.md b/DESCRIPTION.md index 2fce2e903..337960368 100644 --- a/DESCRIPTION.md +++ b/DESCRIPTION.md @@ -28,18 +28,4 @@
  • Automatic word selection.
  • Automatic word division.
  • Word book.
  • - -

    Sponsors

    - - JetBrains - -

    Whatever platform or language you work with, JetBrains has a development tool for you.

    -
    - - CodeStream - -

    -Eliminate context switching and costly distractions. Create and merge PRs and perform code reviews from inside your -IDE while using jump-to-definition, your keybindings, and other IDE favorites. -Learn more. -

    \ No newline at end of file + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 580845593..6b947757d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ pluginGroup = cn.yiiguxing.plugin.translate pluginRepositoryUrl = https://github.com/YiiGuxing/TranslationPlugin # SemVer format -> https://semver.org -pluginMajorVersion = 3.5.6 +pluginMajorVersion = 3.5.7 pluginPreReleaseVersion = pluginBuildMetadata = autoSnapshotVersion = true @@ -32,7 +32,7 @@ platformPlugins = java, org.jetbrains.kotlin, \ kotlin.stdlib.default.dependency = false # Gradle Releases -> https://github.com/gradle/gradle/releases -gradleVersion = 8.4 +gradleVersion = 8.5 # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html org.gradle.configuration-cache = true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index af539450c..30b8ec850 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,17 +1,17 @@ [versions] # libraries -jsoup = "1.16.2" +jsoup = "1.17.2" dbutils = "1.8.1" mp3spi = "1.9.5.4" ideaCompat = "0.0.3" junit = "4.13.2" # plugins -kotlin = "1.9.20" -kover = "0.7.4" +kotlin = "1.9.23" +kover = "0.7.6" qodana = "0.1.13" changelog = "2.2.0" -gradleIntelliJPlugin = "1.16.0" +gradleIntelliJPlugin = "1.17.2" [libraries] jsoup = { group = "org.jsoup", name = "jsoup", version.ref = "jsoup" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c4..d64cd4917 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8838ba97b..e6aba2515 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/TranslationStates.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/TranslationStates.kt index 185b87c98..a5a072e52 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/TranslationStates.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/TranslationStates.kt @@ -32,11 +32,11 @@ class TranslationStates : PersistentStateComponent { var lastReplacementTargetLanguage: Lang? = null var pinTranslationDialog: Boolean = false - var newTranslationDialogX: Int? = null - var newTranslationDialogY: Int? = null - var newTranslationDialogWidth: Int = 600 - var newTranslationDialogHeight: Int = 250 - var newTranslationDialogCollapseDictViewer = true + var translationDialogLocationX: Int? = null + var translationDialogLocationY: Int? = null + var translationDialogWidth: Int = 600 + var translationDialogHeight: Int = 250 + var translationDialogCollapseDictViewer = true /** * 最大历史记录长度 diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/WebPages.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/WebPages.kt index 292792817..c0be61d4a 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/WebPages.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/WebPages.kt @@ -23,6 +23,8 @@ object WebPages { return "$baseUrl/#$langPath/${path.joinToString("/")}" } + fun home(locale: Locale = Locale.getDefault()): String = get(locale = locale) + fun docs(locale: Locale = Locale.getDefault()): String = get("docs", locale = locale) fun updates(version: String = "", locale: Locale = Locale.getDefault()): String { diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/action/TranslateAndReplaceAction.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/action/TranslateAndReplaceAction.kt index 8075a1ad1..e38bf4a23 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/action/TranslateAndReplaceAction.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/action/TranslateAndReplaceAction.kt @@ -15,6 +15,7 @@ import com.intellij.openapi.actionSystem.* import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.WriteAction import com.intellij.openapi.command.CommandProcessor +import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.ScrollType @@ -209,6 +210,8 @@ class TranslateAndReplaceAction : AutoSelectAction(true, NON_WHITESPACE_CONDITIO private companion object { + val logger = logger() + /** 谷歌翻译的空格符:`  -    ` */ val SPACES = Regex("[\u00a0\u2000-\u200a\u202f\u205f\u3000]") @@ -272,7 +275,11 @@ class TranslateAndReplaceAction : AutoSelectAction(true, NON_WHITESPACE_CONDITIO } } - scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE) + try { + scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE) + } catch (e: Exception) { + logger.w("Failed to scroll to caret.", e) + } caretModel.moveToOffset(endOffset) return true diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/activity/BaseStartupActivity.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/activity/BaseStartupActivity.kt index bef745e5d..b155a0059 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/activity/BaseStartupActivity.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/activity/BaseStartupActivity.kt @@ -3,17 +3,21 @@ package cn.yiiguxing.plugin.translate.activity import cn.yiiguxing.plugin.translate.util.Application import com.intellij.openapi.project.Project import com.intellij.openapi.startup.StartupActivity +import java.util.concurrent.atomic.AtomicBoolean abstract class BaseStartupActivity(private val runOnlyOnce: Boolean = false) : StartupActivity { - private var veryFirstProjectOpening: Boolean = true + private var firstProjectOpening = AtomicBoolean(true) final override fun runActivity(project: Project) { - if (Application.isUnitTestMode || (runOnlyOnce && !veryFirstProjectOpening) || project.isDisposed) { + if (Application.isUnitTestMode || project.isDisposed) { + return + } + if (runOnlyOnce && !firstProjectOpening.compareAndSet(true, false)) { return } - veryFirstProjectOpening = false + firstProjectOpening.set(false) if (onBeforeRunActivity(project)) { onRunActivity(project) } diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/diagnostic/ReportCredentials.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/diagnostic/ReportCredentials.kt index d74c9921e..cddaaaeb5 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/diagnostic/ReportCredentials.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/diagnostic/ReportCredentials.kt @@ -6,6 +6,7 @@ import com.intellij.credentialStore.generateServiceName import com.intellij.ide.passwordSafe.PasswordSafe import com.intellij.openapi.components.Service import com.intellij.openapi.components.service +import org.jetbrains.concurrency.runAsync import java.util.* @Service @@ -41,15 +42,17 @@ internal class ReportCredentials private constructor() { get() = PasswordSafe.instance.get(CredentialAttributes(SERVICE_NAME)) ?: anonymousCredentials fun clear() { - PasswordSafe.instance.set(CredentialAttributes(SERVICE_NAME), null) + runAsync { PasswordSafe.instance.set(CredentialAttributes(SERVICE_NAME), null) } lastUserName = "" } fun save(userName: String, token: String) { check(userName.isNotBlank()) { "User name must not be blank" } - val credentials = Credentials(userName, token) - val attributes = CredentialAttributes(SERVICE_NAME, userName) - PasswordSafe.instance.set(attributes, credentials) + runAsync { + val credentials = Credentials(userName, token) + val attributes = CredentialAttributes(SERVICE_NAME, userName) + PasswordSafe.instance.set(attributes, credentials) + } lastUserName = userName } diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/documentation/DocTranslationService.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/documentation/DocTranslationService.kt index 66f3c291f..a13b328d0 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/documentation/DocTranslationService.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/documentation/DocTranslationService.kt @@ -45,8 +45,8 @@ internal class DocTranslationService : Disposable { } override fun dispose() { - translationStates.evictAll() inlayDocTranslations.clear() + translationStates.evictAll() } diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/Languages.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/Languages.kt index 2649ae5b5..6bff83b9e 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/Languages.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/Languages.kt @@ -228,6 +228,12 @@ enum class Lang( /** 库尔德语 */ KURDISH("kurdish", "ku"), + /** 库尔德语(库尔曼吉语) */ + KURDISH_KURMANJI("kurdish.kurmanji", "ku"), + + /** 库尔德语(索拉尼) */ + KURDISH_SORANI("kurdish.sorani", "ckb"), + /** 吉尔吉斯语 */ KYRGYZ("kyrgyz", "ky"), diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/google/GoogleLanguageAdapter.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/google/GoogleLanguageAdapter.kt index a659238ff..d19207320 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/google/GoogleLanguageAdapter.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/google/GoogleLanguageAdapter.kt @@ -20,6 +20,7 @@ object GoogleLanguageAdapter : BaseLanguageAdapter() { Lang.FAROESE, Lang.FIJIAN, Lang.FRENCH_CANADA, + Lang.KURDISH, // 己俱体分为 `库尔德语(库尔曼吉语)` 和 `库尔德语(索拉尼)` Lang.PORTUGUESE_BRAZILIAN, Lang.PORTUGUESE_PORTUGUESE, Lang.SERBIAN_CYRILLIC, diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/MicrosoftLanguageAdapter.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/MicrosoftLanguageAdapter.kt index 14d9d265e..c82f94654 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/MicrosoftLanguageAdapter.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/MicrosoftLanguageAdapter.kt @@ -22,6 +22,7 @@ object MicrosoftLanguageAdapter : BaseLanguageAdapter() { Lang.IGBO, Lang.JAVANESE, Lang.KINYARWANDA, + Lang.KURDISH, // 己俱体分为 `库尔德语(库尔曼吉语)` 和 `库尔德语(索拉尼)` Lang.LATIN, Lang.LUXEMBOURGISH, Lang.PORTUGUESE, @@ -37,6 +38,7 @@ object MicrosoftLanguageAdapter : BaseLanguageAdapter() { Lang.YORUBA, ) + @Suppress("SpellCheckingInspection") override fun getAdaptedLanguages(): Map = mapOf( "yue" to Lang.CHINESE_CANTONESE, "lzh" to Lang.CHINESE_CLASSICAL, @@ -47,6 +49,8 @@ object MicrosoftLanguageAdapter : BaseLanguageAdapter() { "mww" to Lang.HMONG, "mn-Mong" to Lang.MONGOLIAN, "nb" to Lang.NORWEGIAN, + "kmr" to Lang.KURDISH_KURMANJI, + "ku" to Lang.KURDISH_SORANI, "pt" to Lang.PORTUGUESE_BRAZILIAN, ) diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/MicrosoftTranslator.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/MicrosoftTranslator.kt index 33c816de3..3131bfba8 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/MicrosoftTranslator.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/MicrosoftTranslator.kt @@ -2,6 +2,7 @@ package cn.yiiguxing.plugin.translate.trans.microsoft import cn.yiiguxing.plugin.translate.message import cn.yiiguxing.plugin.translate.trans.* +import cn.yiiguxing.plugin.translate.trans.microsoft.data.MicrosoftSourceText import cn.yiiguxing.plugin.translate.trans.microsoft.data.MicrosoftTranslation import cn.yiiguxing.plugin.translate.trans.microsoft.data.TextType import cn.yiiguxing.plugin.translate.trans.microsoft.data.presentableError @@ -65,7 +66,7 @@ object MicrosoftTranslator : AbstractTranslator(), DocumentationTranslator { return Gson().fromJson>(translation, type) .firstOrNull() ?.apply { - this.sourceText = original + this.sourceText = MicrosoftSourceText(original) this.sourceLang = srcLang } ?.toTranslation() diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/MicrosoftTranslatorService.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/MicrosoftTranslatorService.kt index 0e7c7eb1c..5a15f1a7e 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/MicrosoftTranslatorService.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/MicrosoftTranslatorService.kt @@ -61,6 +61,10 @@ internal class MicrosoftTranslatorService { Http.get(AUTH_URL) { accept("*/*") userAgent() + }.also { + if (!it.matches(JWT_REGEX)) { + throw MicrosoftAuthenticationException("Authentication failed: Invalid token.") + } } } .onError { LOG.w("Failed to get access token", it) } @@ -92,7 +96,7 @@ internal class MicrosoftTranslatorService { LOG.warn("Authentication failed", e) val ex = if (e is ExecutionException) e.cause ?: e else e - throw if (ex is IOException) { + throw if (ex !is MicrosoftAuthenticationException && ex is IOException) { MicrosoftAuthenticationException("Authentication failed: ${ex.getCommonMessage()}", ex) } else ex } @@ -127,6 +131,8 @@ internal class MicrosoftTranslatorService { private val GSON: Gson = Gson() private val LOG: Logger = logger() + private val JWT_REGEX = Regex("""^[a-zA-Z0-9\-_]+(\.[a-zA-Z0-9\-_]+){2}$""") + /** * Returns the [MicrosoftTranslatorService] instance. */ diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/data/MicrosoftTranslation.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/data/MicrosoftTranslation.kt index 278473779..50ee90ded 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/data/MicrosoftTranslation.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/trans/microsoft/data/MicrosoftTranslation.kt @@ -7,7 +7,8 @@ import cn.yiiguxing.plugin.translate.trans.microsoft.fromMicrosoftLanguageCode import com.google.gson.annotations.SerializedName data class MicrosoftTranslation( - var sourceText: String, + @SerializedName("sourceText") + var sourceText: MicrosoftSourceText, var sourceLang: Lang, @SerializedName("detectedLanguage") @@ -19,7 +20,7 @@ data class MicrosoftTranslation( override fun toTranslation(): Translation { val translation = translations.first() return Translation( - sourceText, + sourceText.text, translation.text, detectedLanguage?.language?.let { Lang.fromMicrosoftLanguageCode(it) } ?: sourceLang, Lang.fromMicrosoftLanguageCode(translation.to) diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/ui/TranslationDialog.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/ui/TranslationDialog.kt index e48b16025..5f73baa05 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/ui/TranslationDialog.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/ui/TranslationDialog.kt @@ -42,6 +42,7 @@ import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil import icons.TranslationIcons import java.awt.* +import java.awt.datatransfer.DataFlavor import java.awt.datatransfer.StringSelection import java.awt.event.* import javax.swing.* @@ -330,6 +331,12 @@ class TranslationDialog( disabledIcon = AllIcons.Actions.Copy.disabled() addActionListener { copy() } } + val paste = if (isEditable) { + JBMenuItem(message("menu.item.paste"), AllIcons.Actions.MenuPaste).apply { + disabledIcon = AllIcons.Actions.MenuPaste.disabled() + addActionListener { inputTextArea.paste() } + } + } else null val translate = JBMenuItem(message("menu.item.translate"), TranslationIcons.Translation).apply { disabledIcon = TranslationIcons.Translation.disabled() addActionListener { @@ -344,12 +351,16 @@ class TranslationDialog( } add(copy) + paste?.let { add(it) } add(translate) + addPopupMenuListener(object : PopupMenuListenerAdapter() { override fun popupMenuWillBecomeVisible(e: PopupMenuEvent) { val hasSelectedText = !selectedText.isNullOrBlank() copy.isEnabled = hasSelectedText translate.isEnabled = hasSelectedText + paste?.isEnabled = + CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor) != null } }) } @@ -435,12 +446,12 @@ class TranslationDialog( } expandDictViewerButton.setListener({ _, _ -> expandDictViewer() - TranslationStates.newTranslationDialogCollapseDictViewer = false + TranslationStates.translationDialogCollapseDictViewer = false fixWindowHeight() }, null) collapseDictViewerButton.setListener({ _, _ -> collapseDictViewer() - TranslationStates.newTranslationDialogCollapseDictViewer = true + TranslationStates.translationDialogCollapseDictViewer = true fixWindowHeight() }, null) } @@ -501,7 +512,7 @@ class TranslationDialog( } val hasContent = dictDocument != null || extraDocuments.isNotEmpty() - if (hasContent && TranslationStates.newTranslationDialogCollapseDictViewer) + if (hasContent && TranslationStates.translationDialogCollapseDictViewer) collapseDictViewer() else if (hasContent) expandDictViewer() @@ -758,19 +769,19 @@ class TranslationDialog( } private fun storeWindowLocationAndSize() { - TranslationStates.newTranslationDialogX = window.location.x - TranslationStates.newTranslationDialogY = window.location.y - TranslationStates.newTranslationDialogWidth = translationPanel.width - TranslationStates.newTranslationDialogHeight = translationPanel.height + TranslationStates.translationDialogLocationX = window.location.x + TranslationStates.translationDialogLocationY = window.location.y + TranslationStates.translationDialogWidth = translationPanel.width + TranslationStates.translationDialogHeight = translationPanel.height translationPanel.preferredSize = translationPanel.size } private fun restoreWindowLocationAndSize() { - val savedX = TranslationStates.newTranslationDialogX - val savedY = TranslationStates.newTranslationDialogY - val savedWidth = TranslationStates.newTranslationDialogWidth - val savedHeight = TranslationStates.newTranslationDialogHeight + val savedX = TranslationStates.translationDialogLocationX + val savedY = TranslationStates.translationDialogLocationY + val savedWidth = TranslationStates.translationDialogWidth + val savedHeight = TranslationStates.translationDialogHeight if (savedX != null && savedY != null) { val intersectWithScreen = GraphicsEnvironment .getLocalGraphicsEnvironment() @@ -786,8 +797,8 @@ class TranslationDialog( if (intersectWithScreen) { window.location = Point(savedX, savedY) } else { - TranslationStates.newTranslationDialogX = null - TranslationStates.newTranslationDialogY = null + TranslationStates.translationDialogLocationX = null + TranslationStates.translationDialogLocationY = null } } val savedSize = Dimension(savedWidth, savedHeight) diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/update/UpdateManager.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/update/UpdateManager.kt index 3bd2016e1..f0ba2dd5f 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/update/UpdateManager.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/update/UpdateManager.kt @@ -48,7 +48,7 @@ private val borderColor: String get() = (UIManager.getColor("DialogWrapper.southPanelDivider") ?: DEFAULT_BORDER_COLOR).toRGBHex() -class UpdateManager : BaseStartupActivity() { +class UpdateManager : BaseStartupActivity(true) { companion object { internal const val UPDATE_NOTIFICATION_GROUP_ID = "Translation Plugin updated" @@ -80,8 +80,9 @@ class UpdateManager : BaseStartupActivity() { return } + val isFirstInstallation = lastVersionString == Version.INITIAL_VERSION val isFeatureVersion = version.isFeatureUpdateOf(lastVersion) - if (showUpdateNotification(project, plugin, version, isFeatureVersion)) { + if (showUpdateNotification(project, plugin, version, isFeatureVersion, isFirstInstallation)) { properties.setValue(VERSION_PROPERTY, versionString) } } @@ -90,7 +91,8 @@ class UpdateManager : BaseStartupActivity() { project: Project, plugin: IdeaPluginDescriptor, version: Version, - isFeatureVersion: Boolean + isFeatureVersion: Boolean, + isFirstInstallation: Boolean ): Boolean { val title = message( "plugin.name.updated.to.version.notification.title", @@ -134,7 +136,12 @@ class UpdateManager : BaseStartupActivity() { .addAction(GetStartedAction()) .addAction(SupportAction()) .whenExpired { - if (!version.isRreRelease && isFeatureVersion && canBrowseWhatsNewInHTMLEditor) { + if (!canBrowseWhatsNewInHTMLEditor) { + return@whenExpired + } + if (isFirstInstallation) { + WhatsNew.browse(project) { WebPages.home() } + } else if (!version.isRreRelease && isFeatureVersion) { WhatsNew.browse(version, project) } } diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/update/WhatsNew.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/update/WhatsNew.kt index b758f3ee7..e3c61cf48 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/update/WhatsNew.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/update/WhatsNew.kt @@ -23,31 +23,35 @@ internal object WhatsNew { val canBrowseInHTMLEditor: Boolean get() = JBCefApp.isSupported() - fun browse(version: Version, project: Project?) { + fun browse(project: Project?, url: (Boolean) -> String) { if (project != null && canBrowseInHTMLEditor) { invokeLater(ModalityState.NON_MODAL, expired = project.disposed) { try { HTMLEditorProvider.openEditor( project, adaptedMessage("action.WhatsNewInTranslationAction.text", "Translation"), - version.getWhatsNewUrl(), + url(false), //language=HTML """
    |
    Failed to load!
    - | |
    """.trimMargin() ) } catch (e: Throwable) { LOG.w("""Failed to load "What's New" page""", e) - BrowserUtil.browse(version.getWhatsNewUrl(true)) + BrowserUtil.browse(url(true)) } } } else { - BrowserUtil.browse(version.getWhatsNewUrl(true)) + BrowserUtil.browse(url(true)) } } + fun browse(version: Version, project: Project?) { + browse(project) { version.getWhatsNewUrl(it) } + } + private fun Version.getWhatsNewUrl(frame: Boolean = false, locale: Locale = Locale.getDefault()): String { val v = getFeatureUpdateVersion() return if (frame) { diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/util/LruCache.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/util/LruCache.kt index b7a9e9dc2..fe24f5079 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/util/LruCache.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/util/LruCache.kt @@ -69,6 +69,12 @@ open class LruCache(maxSize: Int) { private set @Synchronized get + private var putInCreationCount: Int = 0 + private var removeCount: Int = 0 + private var removeInRemoveIfCount: Int = 0 + private var removeInPutCount: Int = 0 + private var removeInEvictionCount: Int = 0 + /** * The number of times [.get][get] returned a value that was already present in the cache. */ @@ -147,6 +153,7 @@ open class LruCache(maxSize: Int) { map[key] = previous } else { size += safeSizeOf(key, createdValue) + putInCreationCount++ } previous } @@ -170,7 +177,10 @@ open class LruCache(maxSize: Int) { val previous: V? = synchronized(this) { putCount++ size += safeSizeOf(key, value) - map.put(key, value)?.also { size -= safeSizeOf(key, it) } + map.put(key, value)?.also { + size -= safeSizeOf(key, it) + removeInPutCount++ + } } previous?.let { entryRemoved(false, key, it, value) @@ -189,7 +199,10 @@ open class LruCache(maxSize: Int) { while (true) { val toEvict = synchronized(this) { check(!(size < 0 || map.isEmpty() && size != 0)) { - "${javaClass.name}.sizeOf() is reporting inconsistent results! size=$size." + "${javaClass.name}.sizeOf() is reporting inconsistent results! " + + "$this, size=$size, putCount=$putCount, putInCreationCount=$putInCreationCount, " + + "createCount=$createCount, removeCount=$removeCount, removeInPutCount=$removeInPutCount, " + + "removeInRemoveIfCount=$removeInRemoveIfCount, removeInEvictionCount=$removeInEvictionCount." } if (size <= maxSize || map.isEmpty()) { @@ -199,6 +212,7 @@ open class LruCache(maxSize: Int) { val toEvict = map.entries.iterator().next() map.remove(toEvict.key) size -= safeSizeOf(toEvict.key, toEvict.value) + removeInEvictionCount++ evictionCount++ toEvict } @@ -214,7 +228,10 @@ open class LruCache(maxSize: Int) { */ fun remove(key: K & Any): V? { return synchronized(this) { - map.remove(key)?.also { previous -> size -= safeSizeOf(key, previous) } + map.remove(key)?.also { previous -> + size -= safeSizeOf(key, previous) + removeCount++ + } }?.also { previous -> entryRemoved(false, key, previous, null) } @@ -231,8 +248,9 @@ open class LruCache(maxSize: Int) { val entry = each.next() if (predicate(entry.key, entry.value)) { each.remove() - removed.add(entry) size -= safeSizeOf(entry.key, entry.value) + removeInRemoveIfCount++ + removed.add(entry) } } } diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/util/Strings.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/util/Strings.kt index 8601ce175..faedfdc75 100644 --- a/src/main/kotlin/cn/yiiguxing/plugin/translate/util/Strings.kt +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/util/Strings.kt @@ -1,8 +1,3 @@ -/* - * Strings - */ -@file:Suppress("unused") - package cn.yiiguxing.plugin.translate.util import java.net.URLEncoder @@ -13,8 +8,6 @@ private val REGEX_NUM_WORD = Regex("([0-9])([A-Za-z])") private val REGEX_WORD_NUM = Regex("([A-Za-z])([0-9])") private val REGEX_LOWER_UPPER = Regex("([a-z])([A-Z])") private val REGEX_UPPER_WORD = Regex("([A-Z])([A-Z][a-z])") -private val REGEX_WHITESPACE_CHARACTER = Regex("\\s") -private val REGEX_WHITESPACE_CHARACTERS = Regex("\\s+") private val REGEX_SINGLE_LINE = Regex("\\r\\n|\\r|\\n") private val REGEX_COMPRESS_WHITESPACE = Regex("\\s{2,}") private const val REPLACEMENT_SPLIT_GROUP = "$1 $2" @@ -38,27 +31,6 @@ fun String.splitWords(): String { .replace(REGEX_UPPER_WORD, REPLACEMENT_SPLIT_GROUP) } -fun String.filterIgnore(): String { - return try { - Settings.ignoreRegexPattern - ?.let { replace(it, "") } - ?: this - } catch (e: Exception) { - this - } -} - -fun String.processBeforeTranslate(): String? { - val filteredIgnore = filterIgnore() - val formatted = if (!Settings.keepFormat) { - filteredIgnore.replace(REGEX_WHITESPACE_CHARACTERS, " ").trim() - } else filteredIgnore - - return formatted - .takeIf { it.isNotBlank() } - ?.let { if (!it.contains(REGEX_WHITESPACE_CHARACTER)) it.splitWords() else it } -} - /** * 分割句子 * diff --git a/src/main/kotlin/cn/yiiguxing/plugin/translate/util/Translations.kt b/src/main/kotlin/cn/yiiguxing/plugin/translate/util/Translations.kt new file mode 100644 index 000000000..3159cf172 --- /dev/null +++ b/src/main/kotlin/cn/yiiguxing/plugin/translate/util/Translations.kt @@ -0,0 +1,25 @@ +package cn.yiiguxing.plugin.translate.util + +private val REGEX_WHITESPACE_CHARACTER = Regex("\\s") +private val REGEX_WHITESPACE_CHARACTERS = Regex("\\s+") + +fun String.filterIgnore(): String { + return try { + Settings.ignoreRegexPattern + ?.let { replace(it, "") } + ?: this + } catch (e: Exception) { + this + } +} + +fun String.processBeforeTranslate(): String? { + val filteredIgnore = filterIgnore() + val formatted = if (!Settings.keepFormat) { + filteredIgnore.replace(REGEX_WHITESPACE_CHARACTERS, " ").trim() + } else filteredIgnore + + return formatted + .takeIf { it.isNotBlank() } + ?.let { if (!it.contains(REGEX_WHITESPACE_CHARACTER)) it.splitWords() else it } +} \ No newline at end of file diff --git a/src/main/resources/messages/LanguageBundle.properties b/src/main/resources/messages/LanguageBundle.properties index 6382f99a2..af146c542 100644 --- a/src/main/resources/messages/LanguageBundle.properties +++ b/src/main/resources/messages/LanguageBundle.properties @@ -64,6 +64,8 @@ khmer=Khmer kinyarwanda=Kinyarwanda korean=Korean kurdish=Kurdish +kurdish.kurmanji=Kurdish (Kurmanji) +kurdish.sorani=Kurdish (Sorani) kyrgyz=Kyrgyz lao=Lao latin=Latin diff --git a/src/main/resources/messages/LanguageBundle_ja.properties b/src/main/resources/messages/LanguageBundle_ja.properties index bda031ac4..07666463b 100644 --- a/src/main/resources/messages/LanguageBundle_ja.properties +++ b/src/main/resources/messages/LanguageBundle_ja.properties @@ -64,6 +64,8 @@ khmer=クメール語 kinyarwanda=ルワンダ語 korean=韓国語 kurdish=クルド語 +kurdish.kurmanji=クルド語(クルマンジー) +kurdish.sorani=クルド語(ソラニー) kyrgyz=キルギス語 lao=ラオ語 latin=ラテン語 diff --git a/src/main/resources/messages/LanguageBundle_ko.properties b/src/main/resources/messages/LanguageBundle_ko.properties index 842c5da30..3c3c9961b 100644 --- a/src/main/resources/messages/LanguageBundle_ko.properties +++ b/src/main/resources/messages/LanguageBundle_ko.properties @@ -64,6 +64,8 @@ khmer=크메르어 kinyarwanda=키냐르완다 korean=한국어 kurdish=쿠르드어 +kurdish.kurmanji=쿠르드어(쿠르만지) +kurdish.sorani=쿠르드어(소라니) kyrgyz=키르기스어 lao=라오어 latin=라틴어 diff --git a/src/main/resources/messages/LanguageBundle_zh.properties b/src/main/resources/messages/LanguageBundle_zh_CN.properties similarity index 96% rename from src/main/resources/messages/LanguageBundle_zh.properties rename to src/main/resources/messages/LanguageBundle_zh_CN.properties index 8a30f98a8..91fe94618 100644 --- a/src/main/resources/messages/LanguageBundle_zh.properties +++ b/src/main/resources/messages/LanguageBundle_zh_CN.properties @@ -64,6 +64,8 @@ khmer=高棉语 kinyarwanda=卢旺达语 korean=韩语 kurdish=库尔德语 +kurdish.kurmanji=库尔德语(库尔曼吉语) +kurdish.sorani=库尔德语(索拉尼) kyrgyz=吉尔吉斯语 lao=老挝语 latin=拉丁语 diff --git a/src/main/resources/messages/TranslationBundle.properties b/src/main/resources/messages/TranslationBundle.properties index 834d06553..957710cfc 100644 --- a/src/main/resources/messages/TranslationBundle.properties +++ b/src/main/resources/messages/TranslationBundle.properties @@ -14,6 +14,7 @@ label.initializing=Initializing... translation.ui.pane.label.spell=Did you mean: menu.item.copy=Copy menu.item.copy.all=Copy All +menu.item.paste=Paste menu.item.translate=Translate documentation.loading=Loading... settings.page.name=Translation diff --git a/src/main/resources/messages/TranslationBundle_ja.properties b/src/main/resources/messages/TranslationBundle_ja.properties index d71496b65..090c398f1 100644 --- a/src/main/resources/messages/TranslationBundle_ja.properties +++ b/src/main/resources/messages/TranslationBundle_ja.properties @@ -14,6 +14,7 @@ label.initializing=初期化しています... translation.ui.pane.label.spell=もしかして: menu.item.copy=コピー menu.item.copy.all=すべてコピー +menu.item.paste=ペースト menu.item.translate=翻訳 documentation.loading=ページを読み込んでいます... settings.page.name=翻訳 diff --git a/src/main/resources/messages/TranslationBundle_ko.properties b/src/main/resources/messages/TranslationBundle_ko.properties index 47b6baae6..9983bbcdd 100644 --- a/src/main/resources/messages/TranslationBundle_ko.properties +++ b/src/main/resources/messages/TranslationBundle_ko.properties @@ -14,6 +14,7 @@ label.initializing=초기화 중... translation.ui.pane.label.spell=혹시 다음을 의미하셨나요? menu.item.copy=복사 menu.item.copy.all=모두 복사 +menu.item.paste=붙여넣기 menu.item.translate=번역 documentation.loading=로드 중… settings.page.name=번역 diff --git a/src/main/resources/messages/TranslationBundle_zh.properties b/src/main/resources/messages/TranslationBundle_zh_CN.properties similarity index 99% rename from src/main/resources/messages/TranslationBundle_zh.properties rename to src/main/resources/messages/TranslationBundle_zh_CN.properties index e79f207dc..65edf165d 100644 --- a/src/main/resources/messages/TranslationBundle_zh.properties +++ b/src/main/resources/messages/TranslationBundle_zh_CN.properties @@ -14,6 +14,7 @@ label.initializing=初始化中... translation.ui.pane.label.spell=您是不是要找: menu.item.copy=复制 menu.item.copy.all=全部复制 +menu.item.paste=粘贴 menu.item.translate=翻译 documentation.loading=正在加载… settings.page.name=翻译