diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 752e13e..57c0e09 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,12 +46,12 @@ jobs: - name: Gradle Wrapper Validation uses: gradle/wrapper-validation-action@v1.1.0 - # Setup Java 11 environment for the next steps + # Setup 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 + java-version: 17 # Set environment variables - name: Export Properties diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b31a05b..e0a534b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,16 +19,16 @@ jobs: # Check out current repository - name: Fetch Sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.event.release.tag_name }} - # Setup Java 11 environment for the next steps + # Setup 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 + java-version: 17 # Set environment variables - name: Export Properties diff --git a/.github/workflows/run-ui-tests.yml b/.github/workflows/run-ui-tests.yml index 363d9e8..895d61f 100644 --- a/.github/workflows/run-ui-tests.yml +++ b/.github/workflows/run-ui-tests.yml @@ -33,14 +33,14 @@ jobs: # Check out current repository - name: Fetch Sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - # Setup Java 11 environment for the next steps + # Setup 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 + java-version: 17 # Run IDEA prepared for UI testing - name: Run IDE @@ -48,7 +48,7 @@ jobs: # Wait for IDEA to be started - name: Health Check - uses: jtalk/url-health-check-action@v3 + uses: jtalk/url-health-check-action@v4 with: url: http://127.0.0.1:8082 max-attempts: 15 diff --git a/build.gradle.kts b/build.gradle.kts index 720bf3d..cb65999 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,124 +1,223 @@ import org.jetbrains.changelog.Changelog import org.jetbrains.changelog.date import org.jetbrains.changelog.markdownToHTML +import org.jetbrains.intellij.platform.gradle.TestFrameworkType -fun properties(key: String) = project.findProperty(key).toString() +//fun properties(key: String) = project.findProperty(key).toString() +fun properties(key: String) = providers.gradleProperty(key) +fun propertiesGet(key: String) = properties(key).get() plugins { - // Java support - id("java") - // Gradle IntelliJ Plugin - id("org.jetbrains.intellij") version "1.13.3" - // Gradle Changelog Plugin - id("org.jetbrains.changelog") version "2.1.2" + id("java") + alias(libs.plugins.kotlin) // Kotlin support + alias(libs.plugins.intelliJPlatform) // IntelliJ Platform Gradle Plugin + alias(libs.plugins.changelog) // Gradle Changelog Plugin + alias(libs.plugins.qodana) // Gradle Qodana Plugin + alias(libs.plugins.kover) // Gradle Kover Plugin } -group = properties("pluginGroup") -version = properties("pluginVersion") +group = propertiesGet("pluginGroup") +version = propertiesGet("pluginVersion") + +// Set the JVM language level used to build the project. +kotlin { + jvmToolchain(21) +} // Configure project's dependencies repositories { - mavenCentral() + maven("https://developer.huawei.com/repo/") + maven("https://www.jitpack.io") + maven("https://maven.aliyun.com/repository/public") + maven("https://maven.aliyun.com/repository/central") + maven("https://maven.aliyun.com/repository/google") + maven("https://mirrors.tencent.com/nexus/repository/gradle-plugins/") + maven("https://maven.aliyun.com/repository/grails-core") + maven("https://www.jetbrains.com/intellij-repository/releases") // IntelliJ 官方仓库 + maven("https://maven.aliyun.com/repository/gradle-plugin") + maven("https://plugins.gradle.org/m2/") // 官方插件仓库 + gradlePluginPortal() + mavenCentral() + + // IntelliJ Platform Gradle Plugin Repositories Extension - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-repositories-extension.html + intellijPlatform { + defaultRepositories() + } } -// Configure Gradle IntelliJ Plugin - read more: https://github.com/JetBrains/gradle-intellij-plugin -intellij { - pluginName.set(properties("pluginName")) - version.set(properties("platformVersion")) - type.set(properties("platformType")) +dependencies { + // https://mvnrepository.com/artifact/org.junit/junit-bom + testImplementation(platform("org.junit:junit-bom:5.13.0-M2")) + // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") + testRuntimeOnly("org.junit.vintage:junit-vintage-engine") + testImplementation(libs.opentest4j) - // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file. - plugins.set(properties("platformPlugins").split(',').map(String::trim).filter(String::isNotEmpty)) -} + // 声明 IntelliJ IDEA Community 依赖 + intellijPlatform { + androidStudio("2024.3.1.14") + bundledPlugin("org.jetbrains.android") + + /*create(properties("platformType"), properties("platformVersion")) + + // Plugin Dependencies. Uses `platformBundledPlugins` property from the gradle.properties file for bundled IntelliJ Platform plugins. + bundledPlugins(properties("platformBundledPlugins").map { it.split(',') }) + + // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file for plugin from JetBrains Marketplace. + plugins(properties("platformPlugins").map { it.split(',') })*/ + + testFramework(TestFrameworkType.Platform) + + } + + // https://mvnrepository.com/artifact/com.google.auto.service/auto-service-annotations + compileOnly("com.google.auto.service:auto-service-annotations:1.1.1") + // https://mvnrepository.com/artifact/com.google.auto.service/auto-service + annotationProcessor("com.google.auto.service:auto-service:1.1.1") + + // https://mvnrepository.com/artifact/com.google.code.gson/gson + implementation("com.google.code.gson:gson:2.12.1") + implementation("com.squareup.okhttp3:okhttp:4.12.0") + + // https://mvnrepository.com/artifact/com.aliyun/tea-openapi + implementation("com.aliyun:tea-openapi:0.3.7") { + exclude(group = "com.squareup.okhttp3", module = "okhttp") + } + // https://mvnrepository.com/artifact/com.aliyun/alimt20181012 + implementation("com.aliyun:alimt20181012:1.4.0") { + exclude("com.squareup.okhttp3", "okhttp") // 排除旧版 + } -// Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin -changelog { - groups.empty() - header.set(provider { "${version.get()} (${date()})" }) - repositoryUrl.set(properties("pluginRepositoryUrl")) } -tasks { - // Set the JVM compatibility versions - properties("javaVersion").let { - withType { - sourceCompatibility = it - targetCompatibility = it - options.encoding = "UTF-8" +intellijPlatform { + pluginConfiguration { + name = properties("pluginName") + version = properties("platformVersion") + + // Extract the section from README.md and provide for the plugin's manifest + description = providers.fileContents(layout.projectDirectory.file("README.md")).asText.map { + val start = "" + val end = "" + + with(it.lines()) { + if (!containsAll(listOf(start, end))) { + throw GradleException("Plugin description section not found in README.md:\n$start ... $end") + } + subList(indexOf(start) + 1, indexOf(end)).joinToString("\n").let(::markdownToHTML) + } + } + + val changelog = project.changelog // local variable for configuration cache compatibility + // Get the latest available change notes from the changelog file + changeNotes = properties("pluginVersion").map { pluginVersion -> + with(changelog) { + renderItem( + (getOrNull(pluginVersion) ?: getUnreleased()) + .withHeader(false) + .withEmptySections(false), + Changelog.OutputType.HTML, + ) + } + } + + ideaVersion { + sinceBuild = properties("pluginSinceBuild") + untilBuild = properties("pluginUntilBuild") + } + + } + signing { + certificateChain = providers.environmentVariable("CERTIFICATE_CHAIN") + privateKey = providers.environmentVariable("PRIVATE_KEY") + password = providers.environmentVariable("PRIVATE_KEY_PASSWORD") + } + + publishing { + token = providers.environmentVariable("PUBLISH_TOKEN") + // The pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3 + // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more: + // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel + channels = properties("pluginVersion") + .map { listOf(it.substringAfter('-', "").substringBefore('.').ifEmpty { "default" }) } } - } - wrapper { - gradleVersion = properties("gradleVersion") - } + pluginVerification { + ides { +// local(file("D:\\Program Files\\Android\\Android Studio")) +// ide(IntelliJPlatformType.AndroidStudio, "2024.3.1") +// local(file("D:\\Program Files\\JetBrains\\IntelliJ IDEA 2024.3.5\\bin\\idea64.exe")) +// local(file("D:\\Program Files\\Android\\Android Studio\\bin\\studio64.exe")) + recommended() + } + } - patchPluginXml { - version.set(properties("pluginVersion")) - sinceBuild.set(properties("pluginSinceBuild")) - untilBuild.set(properties("pluginUntilBuild")) +} - // Extract the section from README.md and provide for the plugin's manifest - pluginDescription.set( - projectDir.resolve("README.md").readText().lines().run { - val start = "" - val end = "" +// Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin +changelog { + groups.empty() + repositoryUrl.set(properties("pluginRepositoryUrl")) + header.set(provider { "${version.get()} (${date()})" }) +} - if (!containsAll(listOf(start, end))) { - throw GradleException("Plugin description section not found in README.md:\n$start ... $end") +// Configure Gradle Kover Plugin - read more: https://github.com/Kotlin/kotlinx-kover#configuration +kover { + reports { + total { + xml { + onCheck = true + } } - subList(indexOf(start) + 1, indexOf(end)) - }.joinToString("\n").run { markdownToHTML(this) } - ) - - // Get the latest available change notes from the changelog file - changeNotes.set(provider { - with(changelog) { - renderItem( - getOrNull(properties("pluginVersion")) ?: getLatest(), - Changelog.OutputType.HTML, - ) - } - }) - } - - test { - useJUnitPlatform() - } - - // Configure UI tests plugin - // Read more: https://github.com/JetBrains/intellij-ui-test-robot - runIdeForUiTests { - systemProperty("robot-server.port", "8082") - systemProperty("ide.mac.message.dialogs.as.sheets", "false") - systemProperty("jb.privacy.policy.text", "") - systemProperty("jb.consents.confirmation.enabled", "false") - } - - signPlugin { - certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) - privateKey.set(System.getenv("PRIVATE_KEY")) - password.set(System.getenv("PRIVATE_KEY_PASSWORD")) - } - - publishPlugin { - dependsOn("patchChangelog") - token.set(System.getenv("PUBLISH_TOKEN")) - // pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3 - // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more: - // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel - channels.set(listOf(properties("pluginVersion").split('-').getOrElse(1) { "default" }.split('.').first())) - } + } } -dependencies { - // https://github.com/google/auto/tree/master/service - compileOnly("com.google.auto.service:auto-service-annotations:1.1.1") - annotationProcessor("com.google.auto.service:auto-service:1.1.1") +tasks { + + // Set the JVM compatibility versions + propertiesGet("javaVersion").let { + withType { + sourceCompatibility = it + targetCompatibility = it + options.encoding = "UTF-8" + } + } - implementation("com.google.code.gson:gson:2.10.1") - implementation("com.aliyun:alimt20181012:1.0.3") + test { + useJUnitPlatform() + } - testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.3") + wrapper { + gradleVersion = propertiesGet("gradleVersion") + } + publishPlugin { + dependsOn("patchChangelog") + } +} + +intellijPlatformTesting { + runIde { + register("runIdeForUiTests") { + task { + jvmArgumentProviders += CommandLineArgumentProvider { + listOf( + "-Drobot-server.port=8082", + "-Dide.mac.message.dialogs.as.sheets=false", + "-Djb.privacy.policy.text=", + "-Djb.consents.confirmation.enabled=false", + ) + } + } + + plugins { + robotServerPlugin() + } + } + } + testIde + testIdeUi + testIdePerformance } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 3bd9e85..a691f47 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,23 +5,34 @@ pluginGroup = com.airsaid pluginName = AndroidLocalize pluginRepositoryUrl = https://github.com/Airsaid/AndroidLocalizePlugin # SemVer format -> https://semver.org -pluginVersion = 3.0.0 +pluginVersion = 3.0.1 # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html # for insight into build numbers and IntelliJ Platform versions. -pluginSinceBuild = 203 +pluginSinceBuild = 243 pluginUntilBuild = # IntelliJ Platform Properties -> https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties platformType = IC -platformVersion = 2020.3.4 +platformVersion = 2024.3.5 # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html -# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 -platformPlugins = com.intellij.java +# Example: platformPlugins = com.jetbrains.php:203.4449.22, org.intellij.scala:2023.3.27@EAP +platformPlugins = +# Example: platformBundledPlugins = com.intellij.java,org.jetbrains.android +platformBundledPlugins = # Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3 -javaVersion = 11 +javaVersion = 21 # Gradle Releases -> https://github.com/gradle/gradle/releases -gradleVersion = 7.5.1 \ No newline at end of file +gradleVersion = 8.11.1 + +# Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib +kotlin.stdlib.default.dependency=false + +# Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html +org.gradle.configuration-cache=true + +# Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html +org.gradle.caching=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..a5f98e8 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,20 @@ +[versions] +# libraries +opentest4j = "1.3.0" + +# plugins +changelog = "2.2.1" +intelliJPlatform = "2.5.0" +kotlin = "2.1.20" +kover = "0.9.1" +qodana = "2024.3.4" + +[libraries] +opentest4j = { group = "org.opentest4j", name = "opentest4j", version.ref = "opentest4j" } + +[plugins] +changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" } +intelliJPlatform = { id = "org.jetbrains.intellij.platform", version.ref = "intelliJPlatform" } +kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } +qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661..d0aaf77 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Mon Apr 07 14:47:21 CST 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/qodana.yml b/qodana.yml new file mode 100644 index 0000000..b34a45e --- /dev/null +++ b/qodana.yml @@ -0,0 +1,12 @@ +# Qodana configuration: +# https://www.jetbrains.com/help/qodana/qodana-yaml.html + +version: 1.0 +linter: jetbrains/qodana-jvm-community:2024.2 +projectJDK: "17" +profile: + name: qodana.recommended +exclude: + - name: All + paths: + - .qodana \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index c99b672..953efe2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,6 @@ -rootProject.name = "AndroidLocalizePlugin" \ No newline at end of file +rootProject.name = "AndroidLocalizePlugin" + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + diff --git a/src/main/java/com/airsaid/localization/action/TranslateAction.java b/src/main/java/com/airsaid/localization/action/TranslateAction.java index d9b8e16..53451ce 100644 --- a/src/main/java/com/airsaid/localization/action/TranslateAction.java +++ b/src/main/java/com/airsaid/localization/action/TranslateAction.java @@ -23,12 +23,16 @@ import com.airsaid.localization.translate.lang.Lang; import com.airsaid.localization.ui.SelectLanguagesDialog; import com.airsaid.localization.utils.NotificationUtil; +import com.intellij.openapi.actionSystem.ActionUpdateThread; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.XmlRecursiveElementVisitor; import com.intellij.psi.xml.XmlTag; import org.jetbrains.annotations.NotNull; @@ -41,68 +45,120 @@ */ public class TranslateAction extends AnAction implements SelectLanguagesDialog.OnClickListener { - private Project mProject; - private PsiFile mValueFile; - private List mValues; - private final AndroidValuesService mValueService = AndroidValuesService.getInstance(); - - @Override - public void actionPerformed(AnActionEvent e) { - mProject = e.getRequiredData(CommonDataKeys.PROJECT); - mValueFile = e.getRequiredData(CommonDataKeys.PSI_FILE); - - SettingsState.getInstance().initSetting(); - - mValueService.loadValuesByAsync(mValueFile, values -> { - if (!isTranslatable(values)) { - NotificationUtil.notifyInfo(mProject, "The " + mValueFile.getName() + " has no text to translate."); - return; - } - mValues = values; - showSelectLanguageDialog(); - }); - } - - // Verify that there is a text in the value file that needs to be translated. - private boolean isTranslatable(@NotNull List values) { - for (PsiElement psiElement : values) { - if (psiElement instanceof XmlTag) { - if (mValueService.isTranslatable((XmlTag) psiElement)) { - return true; + private Project mProject; + private PsiFile mValueFile; + private List mValues; + private final AndroidValuesService mValueService = AndroidValuesService.getInstance(); + private static final Logger LOG = Logger.getInstance(TranslateAction.class); + + @Override + public void actionPerformed(AnActionEvent e) { + mProject = e.getProject(); + mValueFile = e.getData(CommonDataKeys.PSI_FILE); + + SettingsState.getInstance().initSetting(); + + mValueService.loadValuesByAsync(mValueFile, values -> { + if (!isTranslatable(values)) { + NotificationUtil.notifyInfo(mProject, "The " + mValueFile.getName() + " has no text to translate."); + return; + } + mValues = values; + showSelectLanguageDialog(); + }); + } + + // Verify that there is a text in the value file that needs to be translated. + private boolean isTranslatable(@NotNull List values) { + for (PsiElement psiElement : values) { + if (psiElement instanceof XmlTag) { + if (mValueService.isTranslatable((XmlTag) psiElement)) { + return true; + } + } } - } + return false; + } + + /** + * 检查XML标签是否可翻译 + * + * @param tag XML标签元素 + * @return 如果标签需要翻译返回true + */ + private boolean isTranslatable(@NotNull XmlTag tag) { + // 1. 必须是 标签 + if (!tag.getName().startsWith("string")) + return false; + // 2. translatable 属性不存在 或 不为 false + String translatable = tag.getAttributeValue("translatable"); + return translatable == null || !translatable.equals("false"); + } + + private void showSelectLanguageDialog() { + SelectLanguagesDialog dialog = new SelectLanguagesDialog(mProject); + dialog.setOnClickListener(this); + dialog.show(); + } + + @Override + public @NotNull ActionUpdateThread getActionUpdateThread() { + return ActionUpdateThread.BGT; + } + + @Override + public void update(@NotNull AnActionEvent e) { + // 初始状态设为不可见 + e.getPresentation().setEnabledAndVisible(false); + + Project project = e.getProject(); + if (project == null) return; + + // 在 BGT 线程中安全访问 PSI + ReadAction.run(() -> { + PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE); + if (psiFile == null || !psiFile.isValid()) return; + + // 执行 PSI 检查逻辑 + boolean isValueFile = isValueFile(psiFile); + e.getPresentation().setEnabledAndVisible(isValueFile); + }); + } + + private boolean isValueFile(PsiFile file) { + //return ReadAction.compute(()->mValueService.isValueFile(file)); + return ReadAction.compute(() -> { + if (!file.getName().endsWith(".xml") || !file.isValid()) + return false; + + final boolean[] found = {false}; + file.accept(new XmlRecursiveElementVisitor() { + @Override + public void visitXmlTag(@NotNull XmlTag tag) { + if (found[0]) return; + if (isTranslatable(tag)) + found[0] = true; + super.visitXmlTag(tag); + } + }); + return found[0]; + }); + } + + @Override + public void onClickListener(List selectedLanguage) { + TranslateTask translationTask = new TranslateTask(mProject, "Translating...", selectedLanguage, mValues, mValueFile); + translationTask.setOnTranslateListener(new TranslateTask.OnTranslateListener() { + @Override + public void onTranslateSuccess() { + NotificationUtil.notifyInfo(mProject, "Translation completed!"); + } + + @Override + public void onTranslateError(Throwable e) { + NotificationUtil.notifyError(mProject, "Translation failure: " + e.getLocalizedMessage()); + } + }); + translationTask.queue(); } - return false; - } - - private void showSelectLanguageDialog() { - SelectLanguagesDialog dialog = new SelectLanguagesDialog(mProject); - dialog.setOnClickListener(this); - dialog.show(); - } - - @Override - public void update(@NotNull AnActionEvent e) { - // The translation option is only show when xml file from values is selected - Project project = e.getData(CommonDataKeys.PROJECT); - boolean isSelectValueFile = mValueService.isValueFile(e.getData(CommonDataKeys.PSI_FILE)); - e.getPresentation().setEnabledAndVisible(project != null && isSelectValueFile); - } - - @Override - public void onClickListener(List selectedLanguage) { - TranslateTask translationTask = new TranslateTask(mProject, "Translating...", selectedLanguage, mValues, mValueFile); - translationTask.setOnTranslateListener(new TranslateTask.OnTranslateListener() { - @Override - public void onTranslateSuccess() { - NotificationUtil.notifyInfo(mProject, "Translation completed!"); - } - - @Override - public void onTranslateError(Throwable e) { - NotificationUtil.notifyError(mProject, "Translation failure: " + e.getLocalizedMessage()); - } - }); - translationTask.queue(); - } } diff --git a/src/main/java/com/airsaid/localization/config/SettingsConfigurable.java b/src/main/java/com/airsaid/localization/config/SettingsConfigurable.java index ece66f7..0b4f29e 100644 --- a/src/main/java/com/airsaid/localization/config/SettingsConfigurable.java +++ b/src/main/java/com/airsaid/localization/config/SettingsConfigurable.java @@ -86,7 +86,6 @@ public boolean isModified() { public void apply() throws ConfigurationException { SettingsState settingsState = SettingsState.getInstance(); AbstractTranslator selectedTranslator = settingsComponent.getSelectedTranslator(); - LOG.info("apply selectedTranslator: " + selectedTranslator.getName()); // Verify that the required parameters are not configured if (selectedTranslator.isNeedAppId() && StringUtil.isEmpty(settingsComponent.getAppId())) { diff --git a/src/main/java/com/airsaid/localization/config/SettingsState.java b/src/main/java/com/airsaid/localization/config/SettingsState.java index 20d371a..820c087 100644 --- a/src/main/java/com/airsaid/localization/config/SettingsState.java +++ b/src/main/java/com/airsaid/localization/config/SettingsState.java @@ -21,6 +21,7 @@ import com.airsaid.localization.translate.AbstractTranslator; import com.airsaid.localization.translate.services.TranslatorService; import com.airsaid.localization.utils.SecureStorage; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.text.StringUtil; @@ -35,127 +36,127 @@ * @author airsaid */ @State( - name = "com.airsaid.localization.config.SettingsState", - storages = {@Storage("androidLocalizeSettings.xml")} + name = "com.airsaid.localization.config.SettingsState", + storages = {@Storage("androidLocalizeSettings.xml")} ) @Service public final class SettingsState implements PersistentStateComponent { - private static final Logger LOG = Logger.getInstance(SettingsState.class); + private static final Logger LOG = Logger.getInstance(SettingsState.class); - private final Map appKeyStorage; + private final Map appKeyStorage; - private State state = new State(); + private State state = new State(); - public SettingsState() { - appKeyStorage = new HashMap<>(); - TranslatorService translatorService = TranslatorService.getInstance(); - Collection translators = translatorService.getTranslators().values(); - for (AbstractTranslator translator : translators) { - if (translatorService.getDefaultTranslator() != translator) { - appKeyStorage.put(translator.getKey(), new SecureStorage(translator.getKey())); - } - } - } - - public static SettingsState getInstance() { - return ServiceManager.getService(SettingsState.class); - } - - public void initSetting() { - TranslatorService translatorService = TranslatorService.getInstance(); - AbstractTranslator selectedTranslator = translatorService.getSelectedTranslator(); - if (selectedTranslator == null) { - LOG.info("initSetting"); - translatorService.setSelectedTranslator(getSelectedTranslator()); - translatorService.setEnableCache(isEnableCache()); - translatorService.setMaxCacheSize(getMaxCacheSize()); - translatorService.setTranslationInterval(getTranslationInterval()); - } - - AndroidValuesService.getInstance().setSkipNonTranslatable(isSkipNonTranslatable()); - } - - public AbstractTranslator getSelectedTranslator() { - return StringUtil.isEmpty(state.selectedTranslatorKey) ? TranslatorService.getInstance().getDefaultTranslator() : - TranslatorService.getInstance().getTranslators().get(state.selectedTranslatorKey); - } - - public void setSelectedTranslator(AbstractTranslator translator) { - this.state.selectedTranslatorKey = translator.getKey(); - } - - public void setAppId(@NotNull String translatorKey, @NotNull String appId) { - state.appIds.put(translatorKey, appId); - } - - @NotNull - public String getAppId(String translatorKey) { - String appId = state.appIds.get(translatorKey); - return appId != null ? appId : ""; - } - - public void setAppKey(@NotNull String translatorKey, @NotNull String appKey) { - SecureStorage secureStorage = appKeyStorage.get(translatorKey); - if (secureStorage != null) { - secureStorage.save(appKey); - } - } - - @NotNull - public String getAppKey(@NotNull String translatorKey) { - SecureStorage secureStorage = appKeyStorage.get(translatorKey); - return secureStorage != null ? secureStorage.read() : ""; - } - - public boolean isEnableCache() { - return state.isEnableCache; - } - - public void setEnableCache(boolean isEnable) { - state.isEnableCache = isEnable; - } - - public int getMaxCacheSize() { - return state.maxCacheSize; - } - - public void setMaxCacheSize(int maxCacheSize) { - state.maxCacheSize = maxCacheSize; - } - - public int getTranslationInterval() { - return state.translationInterval; - } - - public void setTranslationInterval(int intervalTime) { - state.translationInterval = intervalTime; - } - - public boolean isSkipNonTranslatable() { - return state.isSkipNonTranslatable; - } - - public void setSkipNonTranslatable(boolean isSkipNonTranslatable) { - state.isSkipNonTranslatable = isSkipNonTranslatable; - } - - @Override - public @Nullable SettingsState.State getState() { - return state; - } - - @Override - public void loadState(@NotNull State state) { - this.state = state; - } - - static class State { - public String selectedTranslatorKey; - public Map appIds = new HashMap<>(); - public boolean isEnableCache = true; - public int maxCacheSize = 500; - public int translationInterval = 2; // 2 second - public boolean isSkipNonTranslatable; - } + public SettingsState() { + appKeyStorage = new HashMap<>(); + TranslatorService translatorService = TranslatorService.getInstance(); + Collection translators = translatorService.getTranslators().values(); + for (AbstractTranslator translator : translators) { + if (translatorService.getDefaultTranslator() != translator) { + appKeyStorage.put(translator.getKey(), new SecureStorage(translator.getKey())); + } + } + } + + public static SettingsState getInstance() { + return ApplicationManager.getApplication().getService(SettingsState.class); + } + + public void initSetting() { + TranslatorService translatorService = TranslatorService.getInstance(); + AbstractTranslator selectedTranslator = translatorService.getSelectedTranslator(); + if (selectedTranslator == null) { + LOG.info("initSetting"); + translatorService.setSelectedTranslator(getSelectedTranslator()); + translatorService.setEnableCache(isEnableCache()); + translatorService.setMaxCacheSize(getMaxCacheSize()); + translatorService.setTranslationInterval(getTranslationInterval()); + } + + AndroidValuesService.getInstance().setSkipNonTranslatable(isSkipNonTranslatable()); + } + + public AbstractTranslator getSelectedTranslator() { + return StringUtil.isEmpty(state.selectedTranslatorKey) ? TranslatorService.getInstance().getDefaultTranslator() : + TranslatorService.getInstance().getTranslators().get(state.selectedTranslatorKey); + } + + public void setSelectedTranslator(AbstractTranslator translator) { + this.state.selectedTranslatorKey = translator.getKey(); + } + + public void setAppId(@NotNull String translatorKey, @NotNull String appId) { + state.appIds.put(translatorKey, appId); + } + + @NotNull + public String getAppId(String translatorKey) { + String appId = state.appIds.get(translatorKey); + return appId != null ? appId : ""; + } + + public void setAppKey(@NotNull String translatorKey, @NotNull String appKey) { + SecureStorage secureStorage = appKeyStorage.get(translatorKey); + if (secureStorage != null) { + secureStorage.save(appKey); + } + } + + @NotNull + public String getAppKey(@NotNull String translatorKey) { + SecureStorage secureStorage = appKeyStorage.get(translatorKey); + return secureStorage != null ? secureStorage.read() : ""; + } + + public boolean isEnableCache() { + return state.isEnableCache; + } + + public void setEnableCache(boolean isEnable) { + state.isEnableCache = isEnable; + } + + public int getMaxCacheSize() { + return state.maxCacheSize; + } + + public void setMaxCacheSize(int maxCacheSize) { + state.maxCacheSize = maxCacheSize; + } + + public int getTranslationInterval() { + return state.translationInterval; + } + + public void setTranslationInterval(int intervalTime) { + state.translationInterval = intervalTime; + } + + public boolean isSkipNonTranslatable() { + return state.isSkipNonTranslatable; + } + + public void setSkipNonTranslatable(boolean isSkipNonTranslatable) { + state.isSkipNonTranslatable = isSkipNonTranslatable; + } + + @Override + public @Nullable SettingsState.State getState() { + return state; + } + + @Override + public void loadState(@NotNull State state) { + this.state = state; + } + + public static class State { + public String selectedTranslatorKey; + public Map appIds = new HashMap<>(); + public boolean isEnableCache = true; + public int maxCacheSize = 500; + public int translationInterval = 2; // 2 second + public boolean isSkipNonTranslatable; + } } diff --git a/src/main/java/com/airsaid/localization/services/AndroidValuesService.java b/src/main/java/com/airsaid/localization/services/AndroidValuesService.java index 1f11926..e78a1dd 100644 --- a/src/main/java/com/airsaid/localization/services/AndroidValuesService.java +++ b/src/main/java/com/airsaid/localization/services/AndroidValuesService.java @@ -20,7 +20,6 @@ import com.airsaid.localization.translate.lang.Lang; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.Service; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; @@ -54,193 +53,192 @@ @Service public final class AndroidValuesService { - private static final Logger LOG = Logger.getInstance(AndroidValuesService.class); - - private static final Pattern STRINGS_FILE_NAME_PATTERN = Pattern.compile(".+\\.xml"); - - private boolean isSkipNonTranslatable; - - /** - * Returns the {@link AndroidValuesService} object instance. - * - * @return the {@link AndroidValuesService} object instance. - */ - public static AndroidValuesService getInstance() { - return ServiceManager.getService(AndroidValuesService.class); - } - - /** - * Asynchronous loading the value file as the {@link PsiElement} collection. - * - * @param valueFile the value file. - * @param consumer load result. called in the event dispatch thread. - */ - public void loadValuesByAsync(@NotNull PsiFile valueFile, @NotNull Consumer> consumer) { - ApplicationManager.getApplication().executeOnPooledThread(() -> { - List values = loadValues(valueFile); - ApplicationManager.getApplication().invokeLater(() -> - consumer.consume(values)); - } - ); - } - - /** - * Loading the value file as the {@link PsiElement} collection. - * - * @param valueFile the value file. - * @return {@link PsiElement} collection. - */ - public List loadValues(@NotNull PsiFile valueFile) { - return ApplicationManager.getApplication().runReadAction((Computable>) () -> { - LOG.info("loadValues valueFile: " + valueFile.getName()); - List values = parseValuesXml(valueFile); - LOG.info("loadValues parsed " + valueFile.getName() + " result: " + values); - return values; - }); - } - - public boolean isSkipNonTranslatable() { - return isSkipNonTranslatable; - } - - public void setSkipNonTranslatable(boolean isSkipNonTranslatable) { - this.isSkipNonTranslatable = isSkipNonTranslatable; - } - - private List parseValuesXml(@NotNull PsiFile valueFile) { - final XmlFile xmlFile = (XmlFile) valueFile; - - final XmlDocument document = xmlFile.getDocument(); - if (document == null) return Collections.emptyList(); - - final XmlTag rootTag = document.getRootTag(); - if (rootTag == null) return Collections.emptyList(); - - PsiElement[] subTags = rootTag.getChildren(); - - if (!isSkipNonTranslatable()) { - return Arrays.asList(subTags); + private static final Logger LOG = Logger.getInstance(AndroidValuesService.class); + + private static final Pattern STRINGS_FILE_NAME_PATTERN = Pattern.compile(".+\\.xml"); + + private boolean isSkipNonTranslatable; + + /** + * Returns the {@link AndroidValuesService} object instance. + * + * @return the {@link AndroidValuesService} object instance. + */ + public static AndroidValuesService getInstance() { + return ApplicationManager.getApplication().getService(AndroidValuesService.class); + } + + /** + * Asynchronous loading the value file as the {@link PsiElement} collection. + * + * @param valueFile the value file. + * @param consumer load result. called in the event dispatch thread. + */ + public void loadValuesByAsync(@NotNull PsiFile valueFile, @NotNull Consumer> consumer) { + ApplicationManager.getApplication().executeOnPooledThread(() -> { + List values = loadValues(valueFile); + ApplicationManager.getApplication().invokeLater(() -> + consumer.consume(values)); + } + ); + } + + /** + * Loading the value file as the {@link PsiElement} collection. + * + * @param valueFile the value file. + * @return {@link PsiElement} collection. + */ + public List loadValues(@NotNull PsiFile valueFile) { + return ApplicationManager.getApplication().runReadAction((Computable>) () -> { + LOG.info("loadValues valueFile: " + valueFile.getName()); + List values = parseValuesXml(valueFile); + LOG.info("loadValues parsed " + valueFile.getName() + " result: " + values); + return values; + }); } - List values = new ArrayList<>(subTags.length); - boolean skipNext = false; + public boolean isSkipNonTranslatable() { + return isSkipNonTranslatable; + } + + public void setSkipNonTranslatable(boolean isSkipNonTranslatable) { + this.isSkipNonTranslatable = isSkipNonTranslatable; + } + + private List parseValuesXml(@NotNull PsiFile valueFile) { + final XmlFile xmlFile = (XmlFile) valueFile; - for (PsiElement e : subTags) { - if (skipNext) { - skipNext = false; - if (!(e instanceof XmlTag)) { - continue; + final XmlDocument document = xmlFile.getDocument(); + if (document == null) return Collections.emptyList(); + + final XmlTag rootTag = document.getRootTag(); + if (rootTag == null) return Collections.emptyList(); + + PsiElement[] subTags = rootTag.getChildren(); + + if (!isSkipNonTranslatable()) { + return Arrays.asList(subTags); } - } - if ((e instanceof XmlTag) && !isTranslatable((XmlTag) e)) { - skipNext = true; - } else { - values.add(e); - } + + List values = new ArrayList<>(subTags.length); + boolean skipNext = false; + + for (PsiElement e : subTags) { + if (skipNext) { + skipNext = false; + if (!(e instanceof XmlTag)) { + continue; + } + } + if ((e instanceof XmlTag) && !isTranslatable((XmlTag) e)) { + skipNext = true; + } else { + values.add(e); + } + } + + return values; + } + + /** + * Write {@link PsiElement} collection data to the specified file. + * + * @param values specified {@link PsiElement} collection data. + * @param valueFile specified file. + */ + public void writeValueFile(@NotNull List values, @NotNull File valueFile) { + boolean isCreateSuccess = FileUtil.createIfDoesntExist(valueFile); + if (!isCreateSuccess) { + LOG.error("Failed to write to " + valueFile.getPath() + " file: create failed!"); + return; + } + ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> { + try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(valueFile, false), StandardCharsets.UTF_8))) { + for (PsiElement value : values) { + bw.write(value.getText()); + } + bw.flush(); + } catch (IOException e) { + LOG.error("Failed to write to " + valueFile.getPath() + " file.", e); + } + })); } - return values; - } - - /** - * Write {@link PsiElement} collection data to the specified file. - * - * @param values specified {@link PsiElement} collection data. - * @param valueFile specified file. - */ - public void writeValueFile(@NotNull List values, @NotNull File valueFile) { - boolean isCreateSuccess = FileUtil.createIfDoesntExist(valueFile); - if (!isCreateSuccess) { - LOG.error("Failed to write to " + valueFile.getPath() + " file: create failed!"); - return; + /** + * Verify that the specified file is a string resource file in the values directory. + * + * @param file the verify file. + * @return true: the file is a string resource file in the values directory. + */ + public boolean isValueFile(@Nullable PsiFile file) { + if (file == null) return false; + + PsiDirectory parent = file.getParent(); + if (parent == null) return false; + + String parentName = parent.getName(); + if (!"values".equals(parentName)) return false; + + String fileName = file.getName(); + return STRINGS_FILE_NAME_PATTERN.matcher(fileName).matches(); } - ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> { - try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(valueFile, false), StandardCharsets.UTF_8))) { - for (PsiElement value : values) { - bw.write(value.getText()); + + /** + * Get the value file of the specified language in the specified project resource directory. + * + * @param project current project. + * @param resourceDir specified resource directory. + * @param lang specified language. + * @param fileName the name of value file. + * @return null if not exist, otherwise return the value file. + */ + @Nullable + public PsiFile getValuePsiFile(@NotNull Project project, + @NotNull VirtualFile resourceDir, + @NotNull Lang lang, + @NotNull String fileName) { + return ApplicationManager.getApplication().runReadAction((Computable) () -> { + VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByIoFile(getValueFile(resourceDir, lang, fileName)); + if (virtualFile == null) { + return null; + } + return PsiManager.getInstance(project).findFile(virtualFile); + }); + } + + /** + * Get the value file in the {@code values} directory of the specified language in the resource directory. + * + * @param resourceDir specified resource directory. + * @param lang specified language. + * @param fileName the name of value file. + * @return the value file. + */ + @NotNull + public File getValueFile(@NotNull VirtualFile resourceDir, @NotNull Lang lang, String fileName) { + return new File(resourceDir.getPath().concat(File.separator).concat(getValuesDirectoryName(lang)), fileName); + } + + private String getValuesDirectoryName(@NotNull Lang lang) { + String[] parts = lang.getCode().split("-"); + if (parts.length > 1) { + return "values-".concat(parts[0] + "-" + "r" + parts[1].toUpperCase()); + } else { + return "values-".concat(lang.getCode()); } - bw.flush(); - } catch (IOException e) { - e.printStackTrace(); - LOG.error("Failed to write to " + valueFile.getPath() + " file.", e); - } - })); - } - - /** - * Verify that the specified file is a string resource file in the values directory. - * - * @param file the verify file. - * @return true: the file is a string resource file in the values directory. - */ - public boolean isValueFile(@Nullable PsiFile file) { - if (file == null) return false; - - PsiDirectory parent = file.getParent(); - if (parent == null) return false; - - String parentName = parent.getName(); - if (!"values".equals(parentName)) return false; - - String fileName = file.getName(); - return STRINGS_FILE_NAME_PATTERN.matcher(fileName).matches(); - } - - /** - * Get the value file of the specified language in the specified project resource directory. - * - * @param project current project. - * @param resourceDir specified resource directory. - * @param lang specified language. - * @param fileName the name of value file. - * @return null if not exist, otherwise return the value file. - */ - @Nullable - public PsiFile getValuePsiFile(@NotNull Project project, - @NotNull VirtualFile resourceDir, - @NotNull Lang lang, - @NotNull String fileName) { - return ApplicationManager.getApplication().runReadAction((Computable) () -> { - VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByIoFile(getValueFile(resourceDir, lang, fileName)); - if (virtualFile == null) { - return null; - } - return PsiManager.getInstance(project).findFile(virtualFile); - }); - } - - /** - * Get the value file in the {@code values} directory of the specified language in the resource directory. - * - * @param resourceDir specified resource directory. - * @param lang specified language. - * @param fileName the name of value file. - * @return the value file. - */ - @NotNull - public File getValueFile(@NotNull VirtualFile resourceDir, @NotNull Lang lang, String fileName) { - return new File(resourceDir.getPath().concat(File.separator).concat(getValuesDirectoryName(lang)), fileName); - } - - private String getValuesDirectoryName(@NotNull Lang lang) { - String[] parts = lang.getCode().split("-"); - if (parts.length > 1) { - return "values-".concat(parts[0] + "-" + "r" + parts[1].toUpperCase()); - } else { - return "values-".concat(lang.getCode()); } - } - - /** - * Returns whether the specified xml tag (string entry) needs to be translated. - * - * @param xmlTag the specified xml tag of string entry. - * @return true: need translation. false: no translation is needed. - */ - public boolean isTranslatable(@NotNull XmlTag xmlTag) { - return ApplicationManager.getApplication().runReadAction((Computable) () -> { - String translatableStr = xmlTag.getAttributeValue("translatable"); - return Boolean.parseBoolean(translatableStr == null ? "true" : translatableStr); - }); - } + + /** + * Returns whether the specified xml tag (string entry) needs to be translated. + * + * @param xmlTag the specified xml tag of string entry. + * @return true: need translation. false: no translation is needed. + */ + public boolean isTranslatable(@NotNull XmlTag xmlTag) { + return ApplicationManager.getApplication().runReadAction((Computable) () -> { + String translatableStr = xmlTag.getAttributeValue("translatable"); + return Boolean.parseBoolean(translatableStr == null ? "true" : translatableStr); + }); + } } diff --git a/src/main/java/com/airsaid/localization/task/TranslateTask.java b/src/main/java/com/airsaid/localization/task/TranslateTask.java index 500869c..3eb9b06 100644 --- a/src/main/java/com/airsaid/localization/task/TranslateTask.java +++ b/src/main/java/com/airsaid/localization/task/TranslateTask.java @@ -54,215 +54,218 @@ */ public class TranslateTask extends Task.Backgroundable { - private static final String NAME_TAG_STRING = "string"; - private static final String NAME_TAG_PLURALS = "plurals"; - private static final String NAME_TAG_STRING_ARRAY = "string-array"; - - private static final Logger LOG = Logger.getInstance(TranslateTask.class); - - private final List mToLanguages; - private final List mValues; - private final VirtualFile mValueFile; - private final TranslatorService mTranslatorService; - private final AndroidValuesService mValueService; - - private OnTranslateListener mOnTranslateListener; - private TranslationException mTranslationError; - - public interface OnTranslateListener { - void onTranslateSuccess(); - - void onTranslateError(Throwable e); - } - - public TranslateTask(@Nullable Project project, @Nls @NotNull String title, List languages, - List values, PsiFile valueFile) { - super(project, title); - mToLanguages = languages; - mValues = values; - mValueFile = valueFile.getVirtualFile(); - mTranslatorService = TranslatorService.getInstance(); - mValueService = AndroidValuesService.getInstance(); - } - - /** - * Set translate result listener. - * - * @param listener callback interface. success or fail. - */ - public void setOnTranslateListener(OnTranslateListener listener) { - mOnTranslateListener = listener; - } - - @Override - public void run(@NotNull ProgressIndicator progressIndicator) { - boolean isOverwriteExistingString = PropertiesComponent.getInstance(myProject) - .getBoolean(Constants.KEY_IS_OVERWRITE_EXISTING_STRING); - LOG.info("run isOverwriteExistingString: " + isOverwriteExistingString); - - for (Lang toLanguage : mToLanguages) { - if (progressIndicator.isCanceled()) break; - - progressIndicator.setText("Translation to " + toLanguage.getEnglishName() + "..."); - - VirtualFile resourceDir = mValueFile.getParent().getParent(); - String valueFileName = mValueFile.getName(); - PsiFile toValuePsiFile = mValueService.getValuePsiFile(myProject, resourceDir, toLanguage, valueFileName); - LOG.info("Translating language: " + toLanguage.getEnglishName() + ", toValuePsiFile: " + toValuePsiFile); - if (toValuePsiFile != null) { - List toValues = mValueService.loadValues(toValuePsiFile); - Map toValuesMap = toValues.stream().collect(Collectors.toMap( - psiElement -> { - if (psiElement instanceof XmlTag) - return ApplicationManager.getApplication().runReadAction((Computable) () -> - ((XmlTag) psiElement).getAttributeValue("name")); - else return UUID.randomUUID().toString(); - }, - Function.identity() - )); - List translatedValues = doTranslate(progressIndicator, toLanguage, toValuesMap, isOverwriteExistingString); - writeTranslatedValues(progressIndicator, new File(toValuePsiFile.getVirtualFile().getPath()), translatedValues); - } else { - List translatedValues = doTranslate(progressIndicator, toLanguage, null, isOverwriteExistingString); - File valueFile = mValueService.getValueFile(resourceDir, toLanguage, valueFileName); - writeTranslatedValues(progressIndicator, valueFile, translatedValues); - } - // If an exception occurs during the translation of the language, - // the translation of the subsequent languages is terminated. - // This prevents the loss of successfully translated strings in that language. - if (mTranslationError != null) { - throw mTranslationError; - } + private static final String NAME_TAG_STRING = "string"; + private static final String NAME_TAG_PLURALS = "plurals"; + private static final String NAME_TAG_STRING_ARRAY = "string-array"; + + private static final Logger LOG = Logger.getInstance(TranslateTask.class); + + private final List mToLanguages; + private final List mValues; + private final VirtualFile mValueFile; + private final TranslatorService mTranslatorService; + private final AndroidValuesService mValueService; + + private OnTranslateListener mOnTranslateListener; + private TranslationException mTranslationError; + + public interface OnTranslateListener { + void onTranslateSuccess(); + + void onTranslateError(Throwable e); } - } - - private List doTranslate(@NotNull ProgressIndicator progressIndicator, - @NotNull Lang toLanguage, - @Nullable Map toValues, - boolean isOverwrite) { - LOG.info("doTranslate toLanguage: " + toLanguage.getEnglishName() + ", toValues: " + toValues + ", isOverwrite: " + isOverwrite); - - List translatedValues = new ArrayList<>(); - for (PsiElement value : mValues) { - if (progressIndicator.isCanceled()) break; - - if (value instanceof XmlTag) { - XmlTag xmlTag = (XmlTag) value; - if (!mValueService.isTranslatable(xmlTag)) { - translatedValues.add(value); - continue; - } - String name = ApplicationManager.getApplication().runReadAction((Computable) () -> - xmlTag.getAttributeValue("name") - ); - if (!isOverwrite && toValues != null && toValues.containsKey(name)) { - translatedValues.add(toValues.get(name)); - continue; - } + public TranslateTask(@Nullable Project project, @Nls @NotNull String title, List languages, + List values, PsiFile valueFile) { + super(project, title); + mToLanguages = languages; + mValues = values; + mValueFile = valueFile.getVirtualFile(); + mTranslatorService = TranslatorService.getInstance(); + mValueService = AndroidValuesService.getInstance(); + } + + /** + * Set translate result listener. + * + * @param listener callback interface. success or fail. + */ + public void setOnTranslateListener(OnTranslateListener listener) { + mOnTranslateListener = listener; + } - XmlTag translateValue = ApplicationManager.getApplication().runReadAction((Computable) () -> - (XmlTag) xmlTag.copy() - ); - translatedValues.add(translateValue); - switch (translateValue.getName()) { - case NAME_TAG_STRING: - doTranslate(progressIndicator, toLanguage, translateValue); - break; - case NAME_TAG_STRING_ARRAY: - case NAME_TAG_PLURALS: - XmlTag[] subTags = ApplicationManager.getApplication() - .runReadAction((Computable) translateValue::getSubTags); - for (XmlTag subTag : subTags) { - doTranslate(progressIndicator, toLanguage, subTag); + @Override + public void run(@NotNull ProgressIndicator progressIndicator) { + if (myProject == null) { + LOG.error("Project is null"); + return; + } + boolean isOverwriteExistingString = PropertiesComponent.getInstance(myProject) + .getBoolean(Constants.KEY_IS_OVERWRITE_EXISTING_STRING); + LOG.info("run isOverwriteExistingString: " + isOverwriteExistingString); + + for (Lang toLanguage : mToLanguages) { + if (progressIndicator.isCanceled()) break; + + progressIndicator.setText("Translation to " + toLanguage.getEnglishName() + "..."); + + VirtualFile resourceDir = mValueFile.getParent().getParent(); + String valueFileName = mValueFile.getName(); + PsiFile toValuePsiFile = mValueService.getValuePsiFile(myProject, resourceDir, toLanguage, valueFileName); + LOG.info("Translating language: " + toLanguage.getEnglishName() + ", toValuePsiFile: " + toValuePsiFile); + if (toValuePsiFile != null) { + List toValues = mValueService.loadValues(toValuePsiFile); + Map toValuesMap = toValues.stream().collect(Collectors.toMap( + psiElement -> { + if (psiElement instanceof XmlTag) + return ApplicationManager.getApplication().runReadAction((Computable) () -> + ((XmlTag) psiElement).getAttributeValue("name")); + else return UUID.randomUUID().toString(); + }, + Function.identity() + )); + List translatedValues = doTranslate(progressIndicator, toLanguage, toValuesMap, isOverwriteExistingString); + writeTranslatedValues(progressIndicator, new File(toValuePsiFile.getVirtualFile().getPath()), translatedValues); + } else { + List translatedValues = doTranslate(progressIndicator, toLanguage, null, isOverwriteExistingString); + File valueFile = mValueService.getValueFile(resourceDir, toLanguage, valueFileName); + writeTranslatedValues(progressIndicator, valueFile, translatedValues); + } + // If an exception occurs during the translation of the language, + // the translation of the subsequent languages is terminated. + // This prevents the loss of successfully translated strings in that language. + if (mTranslationError != null) { + throw mTranslationError; } - break; } - } else { - translatedValues.add(value); - } } - return translatedValues; - } - - private void doTranslate(@NotNull ProgressIndicator progressIndicator, - @NotNull Lang toLanguage, - @NotNull XmlTag xmlTag) { - if (progressIndicator.isCanceled() || isXliffTag(xmlTag)) return; - - XmlTagValue xmlTagValue = ApplicationManager.getApplication() - .runReadAction((Computable) xmlTag::getValue); - XmlTagChild[] children = xmlTagValue.getChildren(); - for (XmlTagChild child : children) { - if (child instanceof XmlText) { - XmlText xmlText = (XmlText) child; - String text = ApplicationManager.getApplication() - .runReadAction((Computable) xmlText::getValue); - if (TextUtil.isEmptyOrSpacesLineBreak(text)) { - continue; + + private List doTranslate(@NotNull ProgressIndicator progressIndicator, + @NotNull Lang toLanguage, + @Nullable Map toValues, + boolean isOverwrite) { + LOG.info("doTranslate toLanguage: " + toLanguage.getEnglishName() + ", toValues: " + toValues + ", isOverwrite: " + isOverwrite); + + List translatedValues = new ArrayList<>(); + for (PsiElement value : mValues) { + if (progressIndicator.isCanceled()) break; + + if (value instanceof XmlTag xmlTag) { + if (!mValueService.isTranslatable(xmlTag)) { + translatedValues.add(value); + continue; + } + + String name = ApplicationManager.getApplication().runReadAction((Computable) () -> + xmlTag.getAttributeValue("name") + ); + if (!isOverwrite && toValues != null && toValues.containsKey(name)) { + translatedValues.add(toValues.get(name)); + continue; + } + + XmlTag translateValue = ApplicationManager.getApplication().runReadAction((Computable) () -> + (XmlTag) xmlTag.copy() + ); + translatedValues.add(translateValue); + switch (translateValue.getName()) { + case NAME_TAG_STRING: + doTranslate(progressIndicator, toLanguage, translateValue); + break; + case NAME_TAG_STRING_ARRAY: + case NAME_TAG_PLURALS: + XmlTag[] subTags = ApplicationManager.getApplication() + .runReadAction((Computable) translateValue::getSubTags); + for (XmlTag subTag : subTags) { + doTranslate(progressIndicator, toLanguage, subTag); + } + break; + } + } else { + translatedValues.add(value); + } } - try { - String translatedText = mTranslatorService.doTranslate(Languages.AUTO, toLanguage, text); - ApplicationManager.getApplication().runReadAction(() -> xmlText.setValue(translatedText)); - } catch (TranslationException e) { - LOG.warn(e); - // Just catch the error and wait for that file to be translated and released. - mTranslationError = e; + return translatedValues; + } + + private void doTranslate(@NotNull ProgressIndicator progressIndicator, + @NotNull Lang toLanguage, + @NotNull XmlTag xmlTag) { + if (progressIndicator.isCanceled() || isXliffTag(xmlTag)) return; + + XmlTagValue xmlTagValue = ApplicationManager.getApplication() + .runReadAction((Computable) xmlTag::getValue); + XmlTagChild[] children = xmlTagValue.getChildren(); + for (XmlTagChild child : children) { + if (child instanceof XmlText xmlText) { + String text = ApplicationManager.getApplication() + .runReadAction((Computable) xmlText::getValue); + if (TextUtil.isEmptyOrSpacesLineBreak(text)) { + continue; + } + try { + String translatedText = mTranslatorService.doTranslate(Languages.AUTO, toLanguage, text); + ApplicationManager.getApplication().runReadAction(() -> xmlText.setValue(translatedText)); + } catch (TranslationException e) { + LOG.warn(e); + // Just catch the error and wait for that file to be translated and released. + mTranslationError = e; + } + } else if (child instanceof XmlTag) { + doTranslate(progressIndicator, toLanguage, (XmlTag) child); + } } - } else if (child instanceof XmlTag) { - doTranslate(progressIndicator, toLanguage, (XmlTag) child); - } } - } - private void writeTranslatedValues(@NotNull ProgressIndicator progressIndicator, - @NotNull File valueFile, - @NotNull List translatedValues) { - LOG.info("writeTranslatedValues valueFile: " + valueFile + ", translatedValues: " + translatedValues); + private void writeTranslatedValues(@NotNull ProgressIndicator progressIndicator, + @NotNull File valueFile, + @NotNull List translatedValues) { + LOG.info("writeTranslatedValues valueFile: " + valueFile + ", translatedValues: " + translatedValues); + + if (progressIndicator.isCanceled() || translatedValues.isEmpty()) return; + + progressIndicator.setText("Writing to " + valueFile.getParentFile().getName() + " data..."); + mValueService.writeValueFile(translatedValues, valueFile); + + refreshAndOpenFile(valueFile); + } - if (progressIndicator.isCanceled() || translatedValues.isEmpty()) return; + private void refreshAndOpenFile(File file) { + if (myProject == null) return; + VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); + boolean isOpenTranslatedFile = PropertiesComponent.getInstance(myProject) + .getBoolean(Constants.KEY_IS_OPEN_TRANSLATED_FILE); + if (virtualFile != null && isOpenTranslatedFile) { + ApplicationManager.getApplication().invokeLater(() -> + FileEditorManager.getInstance(myProject).openFile(virtualFile, true)); + } + } - progressIndicator.setText("Writing to " + valueFile.getParentFile().getName() + " data..."); - mValueService.writeValueFile(translatedValues, valueFile); + private boolean isXliffTag(XmlTag xmlTag) { + return xmlTag != null && "xliff:g".equals(xmlTag.getName()); + } - refreshAndOpenFile(valueFile); - } + @Override + public void onSuccess() { + super.onSuccess(); + translateSuccess(); + } - private void refreshAndOpenFile(File file) { - VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); - boolean isOpenTranslatedFile = PropertiesComponent.getInstance(myProject) - .getBoolean(Constants.KEY_IS_OPEN_TRANSLATED_FILE); - if (virtualFile != null && isOpenTranslatedFile) { - ApplicationManager.getApplication().invokeLater(() -> - FileEditorManager.getInstance(myProject).openFile(virtualFile, true)); + @Override + public void onThrowable(@NotNull Throwable error) { + super.onThrowable(error); + translateError(error); } - } - - private boolean isXliffTag(XmlTag xmlTag) { - return xmlTag != null && "xliff:g".equals(xmlTag.getName()); - } - - @Override - public void onSuccess() { - super.onSuccess(); - translateSuccess(); - } - - @Override - public void onThrowable(@NotNull Throwable error) { - super.onThrowable(error); - translateError(error); - } - - private void translateSuccess() { - if (mOnTranslateListener != null) { - mOnTranslateListener.onTranslateSuccess(); + + private void translateSuccess() { + if (mOnTranslateListener != null) { + mOnTranslateListener.onTranslateSuccess(); + } } - } - private void translateError(Throwable error) { - if (mOnTranslateListener != null) { - mOnTranslateListener.onTranslateError(error); + private void translateError(Throwable error) { + if (mOnTranslateListener != null) { + mOnTranslateListener.onTranslateError(error); + } } - } } diff --git a/src/main/java/com/airsaid/localization/translate/AbstractTranslator.java b/src/main/java/com/airsaid/localization/translate/AbstractTranslator.java index 7e40199..50a7cd9 100644 --- a/src/main/java/com/airsaid/localization/translate/AbstractTranslator.java +++ b/src/main/java/com/airsaid/localization/translate/AbstractTranslator.java @@ -37,114 +37,120 @@ */ public abstract class AbstractTranslator implements Translator, TranslatorConfigurable { - protected static final Logger LOG = Logger.getInstance(AbstractTranslator.class); - - private static final String CONTENT_TYPE = "application/x-www-form-urlencoded"; - - @Override - public String doTranslate(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) throws TranslationException { - checkSupportedLanguages(fromLang, toLang, text); - - String requestUrl = getRequestUrl(fromLang, toLang, text); - RequestBuilder requestBuilder = HttpRequests.post(requestUrl, CONTENT_TYPE); - // Set the timeout time to 60 seconds. - requestBuilder.connectTimeout(60 * 1000); - configureRequestBuilder(requestBuilder); - - try { - return requestBuilder.connect(request -> { - String requestParams = getRequestParams(fromLang, toLang, text) - .stream() - .map(pair -> { - return pair.first.concat("=").concat(URLEncoder.encode(pair.second, StandardCharsets.UTF_8)); - }) - .collect(Collectors.joining("&")); - if (!requestParams.isEmpty()) { - request.write(requestParams); + protected static final Logger LOG = Logger.getInstance(AbstractTranslator.class); + + private static final String CONTENT_TYPE = "application/x-www-form-urlencoded"; + + @Override + public String doTranslate(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) throws TranslationException { + checkSupportedLanguages(fromLang, toLang, text); + + String requestUrl = getRequestUrl(fromLang, toLang, text); + RequestBuilder requestBuilder = HttpRequests.post(requestUrl, CONTENT_TYPE); + // Set the timeout time to 60 seconds. + requestBuilder.connectTimeout(60 * 1000); + if(!isHttpsRequired()){ + requestBuilder.forceHttps(false); } - String requestBody = getRequestBody(fromLang, toLang, text); - if (!requestBody.isEmpty()) { - request.write(requestBody); + configureRequestBuilder(requestBuilder); + + try { + return requestBuilder.connect(request -> { + String requestParams = getRequestParams(fromLang, toLang, text) + .stream() + .map(pair -> pair.first.concat("=") + .concat(URLEncoder.encode(pair.second, StandardCharsets.UTF_8))) + .collect(Collectors.joining("&")); + if (!requestParams.isEmpty()) { + request.write(requestParams); + } + String requestBody = getRequestBody(fromLang, toLang, text); + if (!requestBody.isEmpty()) { + request.write(requestBody); + } + + String resultText = request.readString(); + return parsingResult(fromLang, toLang, text, resultText); + }); + } catch (Exception e) { + LOG.error("do translate failed", e); + throw new TranslationException(fromLang, toLang, text, e); } + } + + @Override + public @Nullable Icon getIcon() { + return null; + } + + @Override + public boolean isNeedAppId() { + return true; + } + + @Override + public @Nullable String getAppId() { + return SettingsState.getInstance().getAppId(getKey()); + } + + @Override + public String getAppIdDisplay() { + return "APP ID"; + } + + @Override + public boolean isNeedAppKey() { + return true; + } + + @Override + public @Nullable String getAppKey() { + return SettingsState.getInstance().getAppKey(getKey()); + } + + @Override + public String getAppKeyDisplay() { + return "APP KEY"; + } - String resultText = request.readString(); - return parsingResult(fromLang, toLang, text, resultText); - }); - } catch (Exception e) { - e.printStackTrace(); - LOG.error(e.getMessage(), e); - throw new TranslationException(fromLang, toLang, text, e); - } - } - - @Override - public @Nullable Icon getIcon() { - return null; - } - - @Override - public boolean isNeedAppId() { - return true; - } - - @Override - public @Nullable String getAppId() { - return SettingsState.getInstance().getAppId(getKey()); - } - - @Override - public String getAppIdDisplay() { - return "APP ID"; - } - - @Override - public boolean isNeedAppKey() { - return true; - } - - @Override - public @Nullable String getAppKey() { - return SettingsState.getInstance().getAppKey(getKey()); - } - - @Override - public String getAppKeyDisplay() { - return "APP KEY"; - } - - @Override - public @Nullable String getApplyAppIdUrl() { - return null; - } - - @NotNull - public String getRequestUrl(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { - throw new UnsupportedOperationException(); - } - - @NotNull - public List> getRequestParams(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { - return List.of(); - } - - @NotNull - public String getRequestBody(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { - return ""; - } - - public void configureRequestBuilder(@NotNull RequestBuilder requestBuilder) { - - } - - @NotNull - public String parsingResult(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text, @NotNull String resultText) { - throw new UnsupportedOperationException(); - } - - protected void checkSupportedLanguages(Lang fromLang, Lang toLang, String text) { - List supportedLanguages = getSupportedLanguages(); - if (!supportedLanguages.contains(toLang)) { - throw new TranslationException(fromLang, toLang, text, toLang.getEnglishName() + " is not supported."); - } - } + @Override + public @Nullable String getApplyAppIdUrl() { + return null; + } + + @NotNull + public String getRequestUrl(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { + throw new UnsupportedOperationException(); + } + + @NotNull + public List> getRequestParams(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { + return List.of(); + } + + @NotNull + public String getRequestBody(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { + return ""; + } + + public void configureRequestBuilder(@NotNull RequestBuilder requestBuilder) { + + } + + @NotNull + public String parsingResult(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text, @NotNull String resultText) { + throw new UnsupportedOperationException(); + } + + protected void checkSupportedLanguages(Lang fromLang, Lang toLang, String text) { + List supportedLanguages = getSupportedLanguages(); + if (!supportedLanguages.contains(toLang)) { + throw new TranslationException(fromLang, toLang, text, toLang.getEnglishName() + " is not supported."); + } + } + + @Override + public boolean isHttpsRequired() { + return true; + } } diff --git a/src/main/java/com/airsaid/localization/translate/TranslationException.java b/src/main/java/com/airsaid/localization/translate/TranslationException.java index 81b4982..8e2b423 100644 --- a/src/main/java/com/airsaid/localization/translate/TranslationException.java +++ b/src/main/java/com/airsaid/localization/translate/TranslationException.java @@ -20,7 +20,6 @@ import com.airsaid.localization.translate.lang.Lang; import com.intellij.openapi.diagnostic.Logger; -import org.apache.http.HttpException; import org.jetbrains.annotations.NotNull; /** @@ -40,7 +39,6 @@ public TranslationException(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNu this.fromLang = fromLang; this.toLang = toLang; this.text = text; - cause.printStackTrace(); LOG.error("TranslationException: " + cause.getMessage(), cause); } diff --git a/src/main/java/com/airsaid/localization/translate/TranslatorConfigurable.java b/src/main/java/com/airsaid/localization/translate/TranslatorConfigurable.java index ca2eac2..a5c885d 100644 --- a/src/main/java/com/airsaid/localization/translate/TranslatorConfigurable.java +++ b/src/main/java/com/airsaid/localization/translate/TranslatorConfigurable.java @@ -57,4 +57,6 @@ public interface TranslatorConfigurable { @Nullable String getApplyAppIdUrl(); + + boolean isHttpsRequired(); } diff --git a/src/main/java/com/airsaid/localization/translate/impl/baidu/BaiduTranslationResult.java b/src/main/java/com/airsaid/localization/translate/impl/baidu/BaiduTranslationResult.java index 53675f4..c1f104b 100644 --- a/src/main/java/com/airsaid/localization/translate/impl/baidu/BaiduTranslationResult.java +++ b/src/main/java/com/airsaid/localization/translate/impl/baidu/BaiduTranslationResult.java @@ -103,7 +103,7 @@ public int hashCode() { if (contents == null || contents.isEmpty()) { return ""; } - String dst = contents.get(0).getDst(); + String dst = contents.getFirst().getDst(); return dst != null ? dst : ""; } diff --git a/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLTranslationResult.java b/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLTranslationResult.java index 1f5a6af..3a29718 100644 --- a/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLTranslationResult.java +++ b/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLTranslationResult.java @@ -33,7 +33,7 @@ public class DeepLTranslationResult implements TranslationResult { @Override public @NotNull String getTranslationResult() { if (translations != null && !translations.isEmpty()) { - String result = translations.get(0).getText(); + String result = translations.getFirst().getText(); return result != null ? result : ""; } return ""; diff --git a/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLTranslator.java b/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLTranslator.java index febad56..356e89a 100644 --- a/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLTranslator.java +++ b/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLTranslator.java @@ -24,7 +24,6 @@ import com.airsaid.localization.translate.util.UrlBuilder; import com.google.auto.service.AutoService; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Pair; import com.intellij.util.io.RequestBuilder; import icons.PluginIcons; import org.jetbrains.annotations.NotNull; @@ -73,6 +72,7 @@ public boolean isNeedAppId() { public @NotNull List getSupportedLanguages() { if (supportedLanguages == null) { supportedLanguages = new ArrayList<>(); + supportedLanguages.add(Languages.ARABIC); supportedLanguages.add(Languages.BULGARIAN); supportedLanguages.add(Languages.CZECH); supportedLanguages.add(Languages.DANISH); @@ -103,7 +103,8 @@ public boolean isNeedAppId() { supportedLanguages.add(Languages.SWEDISH); supportedLanguages.add(Languages.TURKISH); supportedLanguages.add(Languages.UKRAINIAN); - supportedLanguages.add(new Lang(104, "zh", "简体中文", "Chinese Simplified")); + supportedLanguages.add(new Lang(103, "ZH-HANT", "正體中文", "Chinese Traditional")); + supportedLanguages.add(new Lang(104, "ZH-HANS", "简体中文", "Chinese Simplified")); } return supportedLanguages; } @@ -123,19 +124,29 @@ public String getAppKeyDisplay() { return new UrlBuilder(TRANSLATE_URL).build(); } - @Override + /*@Override public @NotNull List> getRequestParams(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { List> params = new ArrayList<>(); params.add(Pair.create("text", text)); params.add(Pair.create("target_lang", toLang.getCode())); return params; + }*/ + + @Override + @NotNull + public String getRequestBody(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { + return "{" + + "\"text\": \"" + text + "\"," + + "\"source_lang\": \"" + fromLang.getCode() + "\"," + + "\"target_lang\": \"" + toLang.getCode() + "\"" + + "}"; } @Override public void configureRequestBuilder(@NotNull RequestBuilder requestBuilder) { requestBuilder.tuner(connection -> { connection.setRequestProperty("Authorization", "DeepL-Auth-Key " + getAppKey()); - connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + connection.setRequestProperty("Content-Type", "application/json"); }); } @@ -145,4 +156,5 @@ public void configureRequestBuilder(@NotNull RequestBuilder requestBuilder) { return GsonUtil.getInstance().getGson().fromJson(resultText, DeepLTranslationResult.class).getTranslationResult(); } + } diff --git a/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLXOfficialTranslator.java b/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLXOfficialTranslator.java new file mode 100644 index 0000000..a92fbf1 --- /dev/null +++ b/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLXOfficialTranslator.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021 Airsaid. https://github.com/airsaid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.airsaid.localization.translate.impl.deepl; + +import com.airsaid.localization.translate.AbstractTranslator; +import com.airsaid.localization.translate.lang.Lang; +import com.google.auto.service.AutoService; +import com.intellij.util.io.RequestBuilder; +import org.jetbrains.annotations.NotNull; + +/** + * @author airsaid + */ +@AutoService(AbstractTranslator.class) +public class DeepLXOfficialTranslator extends DeepLXTranslator { + + private static final String KEY = "DeepLXOfficial"; + private static final String HOST_URL = "https://127.0.0.1:1188/v2"; + private static final String TRANSLATE_URL = HOST_URL.concat("/translate"); + + @Override + public @NotNull String getKey() { + return KEY; + } + + @Override + public @NotNull String getName() { + return KEY; + } + + @Override + public @NotNull String getRequestUrl(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { + return TRANSLATE_URL; + } + + @Override + public void configureRequestBuilder(@NotNull RequestBuilder requestBuilder) { + requestBuilder.tuner(connection -> { + connection.setRequestProperty("Authorization", "DeepL-Auth-Key " + getAppKey()); + connection.setRequestProperty("Content-Type", "application/json"); + }); + } + + @Override + public boolean isNeedAppKey() { + return true; + } +} diff --git a/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLXProTranslator.java b/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLXProTranslator.java new file mode 100644 index 0000000..c4bfcb0 --- /dev/null +++ b/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLXProTranslator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021 Airsaid. https://github.com/airsaid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.airsaid.localization.translate.impl.deepl; + +import com.airsaid.localization.translate.AbstractTranslator; +import com.airsaid.localization.translate.lang.Lang; +import com.google.auto.service.AutoService; +import org.jetbrains.annotations.NotNull; + +/** + * @author airsaid + */ +@AutoService(AbstractTranslator.class) +public class DeepLXProTranslator extends DeepLXTranslator { + + private static final String KEY = "DeepLXPro"; + private static final String HOST_URL = "https://127.0.0.1:1188/v1"; + private static final String TRANSLATE_URL = HOST_URL.concat("/translate"); + + @Override + public @NotNull String getKey() { + return KEY; + } + + @Override + public @NotNull String getName() { + return KEY; + } + + @Override + public @NotNull String getRequestUrl(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { + return TRANSLATE_URL; + } +} diff --git a/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLXTranslator.java b/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLXTranslator.java new file mode 100644 index 0000000..89b564a --- /dev/null +++ b/src/main/java/com/airsaid/localization/translate/impl/deepl/DeepLXTranslator.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021 Airsaid. https://github.com/airsaid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.airsaid.localization.translate.impl.deepl; + +import com.airsaid.localization.translate.AbstractTranslator; +import com.airsaid.localization.translate.lang.Lang; +import com.google.auto.service.AutoService; +import com.intellij.util.io.RequestBuilder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author musagil + */ +@AutoService(AbstractTranslator.class) +public class DeepLXTranslator extends DeepLTranslator { + + private static final String KEY = "DeepLX"; + private static final String HOST_URL = "http://127.0.0.1:1188"; + private static final String TRANSLATE_URL = HOST_URL.concat("/translate"); + protected static final String REPO_URL = "https://api.github.com/repos/OwO-Network/DeepLX/releases/latest"; + + @Override + public @NotNull String getKey() { + return KEY; + } + + @Override + public @NotNull String getName() { + return KEY; + } + + @Override + public @NotNull String getRequestUrl(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { + return TRANSLATE_URL; + } + + @Override + public void configureRequestBuilder(@NotNull RequestBuilder requestBuilder) { + requestBuilder.tuner(connection -> { + connection.setRequestProperty("Authorization", "Bearer " + getAppKey()); + connection.setRequestProperty("Content-Type", "application/json"); + }); + } + + @Override + public @Nullable String getApplyAppIdUrl() { + return null; + } + + @Override + public boolean isNeedAppKey() { + return false; + } + + @Override + public boolean isHttpsRequired() { + return false; + } +} diff --git a/src/main/java/com/airsaid/localization/translate/impl/google/GoogleToken.java b/src/main/java/com/airsaid/localization/translate/impl/google/GoogleToken.java index 0349564..6d5864b 100644 --- a/src/main/java/com/airsaid/localization/translate/impl/google/GoogleToken.java +++ b/src/main/java/com/airsaid/localization/translate/impl/google/GoogleToken.java @@ -134,7 +134,6 @@ private static Pair getTKKFromGoogle() { return Pair.create(value1, value1); } } catch (Exception e) { - e.printStackTrace(); LOG.warn("TKK get failed.", e); } return null; diff --git a/src/main/java/com/airsaid/localization/translate/impl/microsoft/MicrosoftTranslationResult.java b/src/main/java/com/airsaid/localization/translate/impl/microsoft/MicrosoftTranslationResult.java index 0d5c7b9..6fbab10 100644 --- a/src/main/java/com/airsaid/localization/translate/impl/microsoft/MicrosoftTranslationResult.java +++ b/src/main/java/com/airsaid/localization/translate/impl/microsoft/MicrosoftTranslationResult.java @@ -33,7 +33,7 @@ public class MicrosoftTranslationResult implements TranslationResult { @Override public @NotNull String getTranslationResult() { if (translations != null && !translations.isEmpty()) { - String result = translations.get(0).getText(); + String result = translations.getFirst().getText(); return result != null ? result : ""; } return ""; diff --git a/src/main/java/com/airsaid/localization/translate/impl/openai/ChatGPTTranslator.java b/src/main/java/com/airsaid/localization/translate/impl/openai/ChatGPTTranslator.java index f19214a..1b6ece6 100644 --- a/src/main/java/com/airsaid/localization/translate/impl/openai/ChatGPTTranslator.java +++ b/src/main/java/com/airsaid/localization/translate/impl/openai/ChatGPTTranslator.java @@ -60,7 +60,7 @@ public boolean isNeedAppId() { @Override public boolean isNeedAppKey() { - return true; + return super.isNeedAppKey(); } @Override diff --git a/src/main/java/com/airsaid/localization/translate/impl/openai/OpenAIResponse.java b/src/main/java/com/airsaid/localization/translate/impl/openai/OpenAIResponse.java index b31d6da..54c3e2a 100644 --- a/src/main/java/com/airsaid/localization/translate/impl/openai/OpenAIResponse.java +++ b/src/main/java/com/airsaid/localization/translate/impl/openai/OpenAIResponse.java @@ -76,7 +76,7 @@ public void setUsage(Usage usage) { public String getTranslation() { if (choices != null && !choices.isEmpty()) { - String result = choices.get(0).getMessage().getContent(); + String result = choices.getFirst().getMessage().getContent(); return result.trim(); } else { diff --git a/src/main/java/com/airsaid/localization/translate/impl/youdao/YoudaoTranslationResult.java b/src/main/java/com/airsaid/localization/translate/impl/youdao/YoudaoTranslationResult.java index 6c92c54..ea9a637 100644 --- a/src/main/java/com/airsaid/localization/translate/impl/youdao/YoudaoTranslationResult.java +++ b/src/main/java/com/airsaid/localization/translate/impl/youdao/YoudaoTranslationResult.java @@ -78,7 +78,7 @@ public boolean isSuccess() { public @NotNull String getTranslationResult() { List translation = getTranslation(); if (translation != null) { - String result = translation.get(0); + String result = translation.getFirst(); return result != null ? result : ""; } return ""; diff --git a/src/main/java/com/airsaid/localization/translate/lang/Lang.java b/src/main/java/com/airsaid/localization/translate/lang/Lang.java index 1fca7e1..bef4bb2 100644 --- a/src/main/java/com/airsaid/localization/translate/lang/Lang.java +++ b/src/main/java/com/airsaid/localization/translate/lang/Lang.java @@ -17,6 +17,7 @@ package com.airsaid.localization.translate.lang; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.text.StringUtil; import java.util.Objects; @@ -33,6 +34,7 @@ public final class Lang implements Cloneable { private final String name; private final String englishName; private String translationCode; + private static final Logger LOG = Logger.getInstance(Lang.class); public Lang(int id, String code, String name, String englishName) { this.id = id; @@ -88,7 +90,7 @@ public Lang clone() { try { return (Lang) super.clone(); } catch (CloneNotSupportedException e) { - e.printStackTrace(); + LOG.error("Lang clone", e); } return null; } diff --git a/src/main/java/com/airsaid/localization/translate/services/TranslationCacheService.java b/src/main/java/com/airsaid/localization/translate/services/TranslationCacheService.java index 0ff1f3a..25a06a0 100644 --- a/src/main/java/com/airsaid/localization/translate/services/TranslationCacheService.java +++ b/src/main/java/com/airsaid/localization/translate/services/TranslationCacheService.java @@ -21,6 +21,7 @@ import com.airsaid.localization.translate.util.LRUCache; import com.google.gson.reflect.TypeToken; import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.*; import com.intellij.util.xmlb.Converter; import com.intellij.util.xmlb.XmlSerializerUtil; @@ -55,7 +56,7 @@ public final class TranslationCacheService implements PersistentStateComponent lruCache = new LRUCache<>(CACHE_MAX_SIZE); public static TranslationCacheService getInstance() { - return ServiceManager.getService(TranslationCacheService.class); + return ApplicationManager.getApplication().getService(TranslationCacheService.class); } public void put(@NotNull String key, @NotNull String value) { diff --git a/src/main/java/com/airsaid/localization/translate/services/TranslatorService.java b/src/main/java/com/airsaid/localization/translate/services/TranslatorService.java index 4310052..f6dffc3 100644 --- a/src/main/java/com/airsaid/localization/translate/services/TranslatorService.java +++ b/src/main/java/com/airsaid/localization/translate/services/TranslatorService.java @@ -23,9 +23,8 @@ import com.airsaid.localization.translate.lang.Lang; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.Service; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,123 +37,123 @@ @Service public final class TranslatorService { - private static final Logger LOG = Logger.getInstance(TranslatorService.class); - - private AbstractTranslator selectedTranslator; - private final AbstractTranslator defaultTranslator; - private final TranslationCacheService cacheService; - private final Map translators; - private final List translationInterceptors; - private boolean isEnableCache = true; - private int intervalTime; - - public interface TranslationInterceptor { - String process(String text); - } - - public TranslatorService() { - translators = new LinkedHashMap<>(); - ServiceLoader serviceLoader = ServiceLoader.load( - AbstractTranslator.class, getClass().getClassLoader() - ); - for (AbstractTranslator translator : serviceLoader) { - translators.put(translator.getKey(), translator); + private static final Logger LOG = Logger.getInstance(TranslatorService.class); + + private AbstractTranslator selectedTranslator; + private final AbstractTranslator defaultTranslator; + private final TranslationCacheService cacheService; + private final Map translators; + private final List translationInterceptors; + private boolean isEnableCache = true; + private int intervalTime; + + public interface TranslationInterceptor { + String process(String text); } - defaultTranslator = translators.get(GoogleTranslator.KEY); - cacheService = TranslationCacheService.getInstance(); + public TranslatorService() { + translators = new LinkedHashMap<>(); + ServiceLoader serviceLoader = ServiceLoader.load( + AbstractTranslator.class, getClass().getClassLoader() + ); + for (AbstractTranslator translator : serviceLoader) { + translators.put(translator.getKey(), translator); + } + defaultTranslator = translators.get(GoogleTranslator.KEY); + + cacheService = TranslationCacheService.getInstance(); + + translationInterceptors = new ArrayList<>(); + translationInterceptors.add(new EscapeCharactersInterceptor()); + } - translationInterceptors = new ArrayList<>(); - translationInterceptors.add(new EscapeCharactersInterceptor()); - } + @NotNull + public static TranslatorService getInstance() { + return ApplicationManager.getApplication().getService(TranslatorService.class); + } - @NotNull - public static TranslatorService getInstance() { - return ServiceManager.getService(TranslatorService.class); - } + public AbstractTranslator getDefaultTranslator() { + return defaultTranslator; + } - public AbstractTranslator getDefaultTranslator() { - return defaultTranslator; - } + public Map getTranslators() { + return translators; + } - public Map getTranslators() { - return translators; - } + public void setSelectedTranslator(@NotNull AbstractTranslator selectedTranslator) { + if (this.selectedTranslator != selectedTranslator) { + LOG.info(String.format("setTranslator: %s", selectedTranslator)); + this.selectedTranslator = selectedTranslator; + } + } - public void setSelectedTranslator(@NotNull AbstractTranslator selectedTranslator) { - if (this.selectedTranslator != selectedTranslator) { - LOG.info(String.format("setTranslator: %s", selectedTranslator)); - this.selectedTranslator = selectedTranslator; + @Nullable + public AbstractTranslator getSelectedTranslator() { + return selectedTranslator; } - } - - @Nullable - public AbstractTranslator getSelectedTranslator() { - return selectedTranslator; - } - - public void doTranslateByAsync(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text, @NotNull Consumer consumer) { - ApplicationManager.getApplication().executeOnPooledThread(() -> { - final String translatedText = doTranslate(fromLang, toLang, text); - ApplicationManager.getApplication().invokeLater(() -> - consumer.accept(translatedText)); - }); - } - - public String doTranslate(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { - LOG.info(String.format("doTranslate fromLang: %s, toLang: %s, text: %s", fromLang, toLang, text)); - - if (isEnableCache) { - String cacheResult = cacheService.get(getCacheKey(fromLang, toLang, text)); - if (!cacheResult.isEmpty()) { - LOG.info(String.format("doTranslate cache result: %s", cacheResult)); - return cacheResult; - } + + public void doTranslateByAsync(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text, @NotNull Consumer consumer) { + ApplicationManager.getApplication().executeOnPooledThread(() -> { + final String translatedText = doTranslate(fromLang, toLang, text); + ApplicationManager.getApplication().invokeLater(() -> + consumer.accept(translatedText)); + }); } - // Arabic numbers skip translation - if (StringUtils.isNumeric(text)) { - return text; + public String doTranslate(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { + LOG.info(String.format("doTranslate fromLang: %s, toLang: %s, text: %s", fromLang, toLang, text)); + + if (isEnableCache) { + String cacheResult = cacheService.get(getCacheKey(fromLang, toLang, text)); + if (!cacheResult.isEmpty()) { + LOG.info(String.format("doTranslate cache result: %s", cacheResult)); + return cacheResult; + } + } + + // Arabic numbers skip translation + if (StringUtils.isNumeric(text)) { + return text; + } + + String result = selectedTranslator.doTranslate(fromLang, toLang, text); + LOG.info(String.format("doTranslate result: %s", result)); + for (TranslationInterceptor interceptor : translationInterceptors) { + result = interceptor.process(result); + LOG.info(String.format("doTranslate interceptor process result: %s", result)); + } + cacheService.put(getCacheKey(fromLang, toLang, text), result); + delay(intervalTime); + return result; } - String result = selectedTranslator.doTranslate(fromLang, toLang, text); - LOG.info(String.format("doTranslate result: %s", result)); - for (TranslationInterceptor interceptor : translationInterceptors) { - result = interceptor.process(result); - LOG.info(String.format("doTranslate interceptor process result: %s", result)); + public void setEnableCache(boolean isEnableCache) { + this.isEnableCache = isEnableCache; } - cacheService.put(getCacheKey(fromLang, toLang, text), result); - delay(intervalTime); - return result; - } - - public void setEnableCache(boolean isEnableCache) { - this.isEnableCache = isEnableCache; - } - - public boolean isEnableCache() { - return isEnableCache; - } - - public void setMaxCacheSize(int maxCacheSize) { - cacheService.setMaxCacheSize(maxCacheSize); - } - - public void setTranslationInterval(int intervalTime) { - this.intervalTime = intervalTime; - } - - private String getCacheKey(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { - return fromLang.getCode() + "_" + toLang.getCode() + "_" + text; - } - - private void delay(int second) { - if (second <= 0) return; - try { - LOG.info(String.format("doTranslate delay time: %d second.", second)); - Thread.sleep(second * 1000L); - } catch (InterruptedException e) { - e.printStackTrace(); + + public boolean isEnableCache() { + return isEnableCache; + } + + public void setMaxCacheSize(int maxCacheSize) { + cacheService.setMaxCacheSize(maxCacheSize); + } + + public void setTranslationInterval(int intervalTime) { + this.intervalTime = intervalTime; + } + + private String getCacheKey(@NotNull Lang fromLang, @NotNull Lang toLang, @NotNull String text) { + return fromLang.getCode() + "_" + toLang.getCode() + "_" + text; + } + + private void delay(int second) { + if (second <= 0) return; + try { + LOG.info(String.format("doTranslate delay time: %d second.", second)); + Thread.sleep(second * 1000L); + } catch (InterruptedException e) { + LOG.error("doTranslate delay interrupted.", e); + } } - } } diff --git a/src/main/java/com/airsaid/localization/translate/util/GitHubReleaseDownloader.java b/src/main/java/com/airsaid/localization/translate/util/GitHubReleaseDownloader.java new file mode 100644 index 0000000..7f2b0a8 --- /dev/null +++ b/src/main/java/com/airsaid/localization/translate/util/GitHubReleaseDownloader.java @@ -0,0 +1,183 @@ +package com.airsaid.localization.translate.util; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.intellij.openapi.diagnostic.Logger; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.InputStream; +import java.net.ProxySelector; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.concurrent.TimeUnit; + +public class GitHubReleaseDownloader { + protected static final Logger LOG = Logger.getInstance(GitHubReleaseDownloader.class); + private static final OkHttpClient mClient = new OkHttpClient.Builder() + .proxySelector(ProxySelector.getDefault()) + .build(); + + private static final int MAX_RETRIES = 3; // 最大重试次数 + private static final long BASE_TIMEOUT_SEC = 10; // 基础超时时间 + private static final double TIMEOUT_BACKOFF = 1.5; // 超时时间指数退避因子 + + public static void getRelease(@NotNull String repoUrl) { + try { + // 1. 获取最新版本信息 + JsonObject release = getLatestRelease(repoUrl); + String version = release.get("tag_name").getAsString(); + System.out.println("最新版本: " + version); + + // 2. 获取当前系统信息 + String os = getNormalizedOS(); + String arch = getNormalizedArch(); + System.out.println("检测到系统: " + os + " " + arch); + + // 3. 查找匹配的资产文件 + JsonArray assets = release.getAsJsonArray("assets"); + String downloadUrl = findMatchingAsset(assets, os, arch); + if (downloadUrl == null) { + throw new RuntimeException("未找到匹配的下载文件"); + } + // https://github.com/OwO-Network/DeepLX/releases/download/v1.0.7/deeplx_windows_amd64.exe + // https://github.moeyy.xyz/ + LOG.info("Download url: " + downloadUrl); + downloadUrl = "https://github.moeyy.xyz/" + downloadUrl; + + // 4. 下载文件 + String fileName = downloadUrl.substring(downloadUrl.lastIndexOf('/') + 1); + int attempt = 0; + while (attempt < MAX_RETRIES) { + try { + attempt++; + long currentTimeout = (long) (BASE_TIMEOUT_SEC * Math.pow(TIMEOUT_BACKOFF, attempt - 1)); + System.out.printf("第 %d 次尝试 (超时: 连接 %ds/读取 %ds)...%n", + attempt, currentTimeout, currentTimeout * 2); + OkHttpClient client = new OkHttpClient.Builder() + .proxySelector(ProxySelector.getDefault()) + .connectTimeout(currentTimeout, TimeUnit.SECONDS) + .readTimeout(currentTimeout * 2, TimeUnit.SECONDS) + .build(); + downloadFile(client, downloadUrl, fileName); + client.dispatcher().executorService().shutdown(); + System.out.println("下载成功!文件保存至: " + fileName); + return; + } catch (IOException e) { + handlerError(e, attempt); + } + } + + } catch (Exception e) { + LOG.error("getRelease Error", e); + } + } + + private static JsonObject getLatestRelease(@NotNull String repoUrl) throws IOException { + Request request = new Request.Builder() + .url(repoUrl) + .header("User-Agent", "Java/DeepLX-Updater")// GitHub要求User-Agent + .build(); + + try (Response response = mClient.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Request failed code " + response); + } + ResponseBody body = response.body(); + if (body == null) { + throw new IOException("Response body is null"); + } + return GsonUtil.getInstance().getGson().fromJson(body.charStream(), JsonObject.class); + } + } + + private static String getNormalizedOS() { + String osName = System.getProperty("os.name").toLowerCase(); + if (osName.contains("win")) return "windows"; + if (osName.contains("linux")) return "linux"; + if (osName.contains("mac")) return "darwin"; + return "unknown"; + } + + private static String getNormalizedArch() { + String osArch = System.getProperty("os.arch").toLowerCase(); + if (osArch.contains("aarch64") || osArch.contains("arm64")) return "arm64"; + if (osArch.contains("amd64") || osArch.contains("x86_64")) return "amd64"; + return "unknown"; + } + + private static String findMatchingAsset(JsonArray assets, String os, String arch) { + for (int i = 0; i < assets.size(); i++) { + JsonObject asset = assets.get(i).getAsJsonObject(); + String name = asset.get("name").getAsString().toLowerCase(); + String url = asset.get("browser_download_url").getAsString(); + + // 匹配规则示例: deeplx_windows_amd64.exe + if (name.contains(os) && name.contains(arch)) { + return url; + } + } + return null; + } + + private static void downloadFile(OkHttpClient client, String fileUrl, String savePath) throws IOException { + Request request = new Request.Builder() + .header("User-Agent", "Java/DeepLX-Updater") + .url(fileUrl) + .build(); + try (Response response = client.newCall(request).execute()) { + validateResponse(response); + if (!response.isSuccessful()) { + throw new IOException("Request failed code " + response); + } + ResponseBody body = response.body(); + if (body == null) { + throw new IOException("Response body is null"); + } + try (InputStream in = body.byteStream()) { + Files.copy(in, Paths.get(savePath), StandardCopyOption.REPLACE_EXISTING); + } + } + } + + private static void handlerError(IOException e, int attempt) { + String errorType = "Network error"; + if (e.getMessage().contains("timeout")) { + errorType = e.getMessage().contains("connect") ? "connect timeout" : "read timeout"; + } + LOG.error("error: " + errorType + "; msg: " + e.getMessage()); + if (attempt < 10) { + LOG.info("waiting try"); + try { + Thread.sleep(1000 * (long) Math.pow(2, attempt)); + } catch (Exception ex) { + Thread.currentThread().interrupt(); + } + } + } + + private static void validateResponse(Response response) throws IOException { + if (!response.isSuccessful()) { + throw new IOException("Http code " + response.code() + ": " + response.message()); + } + String contentLength = response.header("Content-Length"); + if (contentLength == null) { + LOG.warn("Warning: Server is not return file size"); + } else { + LOG.info("Content-Length: " + formatFileSize(Long.parseLong(contentLength))); + } + } + + private static String formatFileSize(long bytes) { + if (bytes < 1024) { + return bytes + "B"; + } + int exp = (int) (Math.log10(bytes) / Math.log10(1024)); + return String.format("%.1f %sB", bytes / Math.pow(1024, exp), "KMGTPE".charAt(exp - 1)); + } +} diff --git a/src/main/java/com/airsaid/localization/translate/util/UrlBuilder.java b/src/main/java/com/airsaid/localization/translate/util/UrlBuilder.java index 05ae764..d4172a5 100644 --- a/src/main/java/com/airsaid/localization/translate/util/UrlBuilder.java +++ b/src/main/java/com/airsaid/localization/translate/util/UrlBuilder.java @@ -22,47 +22,46 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; /** * @author airsaid */ public class UrlBuilder { - private final String baseUrl; - private final List> queryParameters; + private final String baseUrl; + private final List> queryParameters; - public UrlBuilder(String baseUrl) { - this.baseUrl = baseUrl; - queryParameters = new ArrayList<>(); - } + public UrlBuilder(String baseUrl) { + this.baseUrl = baseUrl; + queryParameters = new ArrayList<>(); + } - public UrlBuilder addQueryParameter(String key, String value) { - queryParameters.add(Pair.create(key, value)); - return this; - } + public UrlBuilder addQueryParameter(String key, String value) { + queryParameters.add(Pair.create(key, value)); + return this; + } - public UrlBuilder addQueryParameters(String key, String... values) { - queryParameters.addAll(Arrays.stream(values).map(value -> Pair.create(key, value)).collect(Collectors.toList())); - return this; - } + public UrlBuilder addQueryParameters(String key, String... values) { + queryParameters.addAll(Arrays.stream(values).map(value -> Pair.create(key, value)).toList()); + return this; + } - public String build() { - StringBuilder result = new StringBuilder(baseUrl); - for (int i = 0; i < queryParameters.size(); i++) { - if (i == 0) { - result.append("?"); - } else { - result.append("&"); - } - Pair param = queryParameters.get(i); - String key = param.first; - String value = param.second; - result.append(key) - .append("=") - .append(value); + public String build() { + StringBuilder result = new StringBuilder(baseUrl); + for (int i = 0; i < queryParameters.size(); i++) { + if (i == 0) { + result.append("?"); + } else { + result.append("&"); + } + Pair param = queryParameters.get(i); + String key = param.first; + String value = param.second; + result.append(key) + .append("=") + .append(value); + } + return result.toString(); } - return result.toString(); - } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 8d448a4..c2bfded 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -1,14 +1,35 @@ + + com.github.airsaid.androidlocalize + + AndroidLocalize + + Airsaid + + + + + + + com.intellij.modules.platform + - + diff --git a/src/test/java/com/airsaid/localization/translate/impl/deeplx/DeepLXTest.java b/src/test/java/com/airsaid/localization/translate/impl/deeplx/DeepLXTest.java new file mode 100644 index 0000000..321790a --- /dev/null +++ b/src/test/java/com/airsaid/localization/translate/impl/deeplx/DeepLXTest.java @@ -0,0 +1,13 @@ +package com.airsaid.localization.translate.impl.deeplx; + +import com.airsaid.localization.translate.util.GitHubReleaseDownloader; +import org.junit.jupiter.api.Test; + +public class DeepLXTest { + @Test + public void DeepLXTranslate() { + final String REPO_URL = "https://api.github.com/repos/OwO-Network/DeepLX/releases/latest"; + + GitHubReleaseDownloader.getRelease(REPO_URL); + } +}