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
-
-
-
-Whatever platform or language you work with, JetBrains has a development tool for you.
-
-
-
-
-
-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
"""""".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=翻译