Airsaid / AndroidLocalizePlugin

:earth_asia: Android localization plugin. supports multiple languages and multiple translators.
https://plugins.jetbrains.com/plugin/11174-androidlocalize
Apache License 2.0
589 stars 91 forks source link

add ChatGPT as translation option #118

Closed rbenza closed 1 year ago

rbenza commented 1 year ago

@Airsaid I still get the error below when trying to translate a strings.xml file. I'm not fully sure if I need to add code to the SettingsComponent class. Maybe you can edit / provide feedback?

com.intellij.diagnostic.PluginException: Cannot create class com.airsaid.localization.config.SettingsState (classloader=PluginClassLoader(plugin=PluginDescriptor(name=AndroidLocalize, id=com.github.airsaid.androidlocalize, descriptorPath=plugin.xml, path=~\AppData\Local\JetBrains\Toolbox\apps\AndroidStudio\ch-1\223.8617.56.2231.9687552.plugins\AndroidLocalizePlugin-3.0.0.jar, version=3.0.0, package=null, isBundled=false), packagePrefix=null, instanceId=137, state=active))
    at com.intellij.serviceContainer.ComponentManagerImpl.instantiateClass(ComponentManagerImpl.kt:989)
    at com.intellij.serviceContainer.ComponentManagerImpl.createLightService(ComponentManagerImpl.kt:929)
    at com.intellij.serviceContainer.ComponentManagerImpl.getOrCreateLightService(ComponentManagerImpl.kt:733)
    at com.intellij.serviceContainer.ComponentManagerImpl.doGetService(ComponentManagerImpl.kt:685)
    at com.intellij.serviceContainer.ComponentManagerImpl.getService(ComponentManagerImpl.kt:629)
    at com.intellij.openapi.components.ServiceManager.getService(ServiceManager.java:19)
    at com.airsaid.localization.config.SettingsState.getInstance(SettingsState.java:61)
    at com.airsaid.localization.action.TranslateAction.actionPerformed(TranslateAction.java:54)
    at com.intellij.openapi.actionSystem.ex.ActionUtil.doPerformActionOrShowPopup(ActionUtil.java:327)
    at com.intellij.openapi.actionSystem.ex.ActionUtil.lambda$performActionDumbAwareWithCallbacks$4(ActionUtil.java:306)
    at com.intellij.openapi.actionSystem.ex.ActionUtil.performDumbAwareWithCallbacks(ActionUtil.java:350)
    at com.intellij.openapi.actionSystem.ex.ActionUtil.performActionDumbAwareWithCallbacks(ActionUtil.java:306)
    at com.intellij.openapi.actionSystem.impl.ActionMenuItem.lambda$performAction$5(ActionMenuItem.java:296)
    at com.intellij.openapi.wm.impl.FocusManagerImpl.runOnOwnContext(FocusManagerImpl.java:226)
    at com.intellij.openapi.actionSystem.impl.ActionMenuItem.performAction(ActionMenuItem.java:289)
    at com.intellij.openapi.actionSystem.impl.ActionMenuItem.lambda$new$0(ActionMenuItem.java:64)
    at java.desktop/javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1972)
    at com.intellij.openapi.actionSystem.impl.ActionMenuItem.lambda$fireActionPerformed$4(ActionMenuItem.java:111)
    at com.intellij.openapi.application.TransactionGuardImpl.performActivity(TransactionGuardImpl.java:105)
    at com.intellij.openapi.application.TransactionGuardImpl.performUserActivity(TransactionGuardImpl.java:94)
    at com.intellij.openapi.actionSystem.impl.ActionMenuItem.fireActionPerformed(ActionMenuItem.java:111)
    at com.intellij.ui.plaf.beg.BegMenuItemUI.doClick(BegMenuItemUI.java:526)
    at com.intellij.ui.plaf.beg.BegMenuItemUI$MyMouseInputHandler.mouseReleased(BegMenuItemUI.java:558)
    at java.desktop/java.awt.Component.processMouseEvent(Component.java:6656)
    at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3385)
    at java.desktop/java.awt.Component.processEvent(Component.java:6421)
    at java.desktop/java.awt.Container.processEvent(Container.java:2266)
    at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5026)
    at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
    at java.desktop/java.awt.Component.dispatchEvent(Component.java:4854)
    at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4948)
    at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4575)
    at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4516)
    at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2310)
    at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2804)
    at java.desktop/java.awt.Component.dispatchEvent(Component.java:4854)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:788)
    at java.desktop/java.awt.EventQueue$3.run(EventQueue.java:737)
    at java.desktop/java.awt.EventQueue$3.run(EventQueue.java:731)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:97)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:761)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:759)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:758)
    at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:909)
    at com.intellij.ide.IdeEventQueue.dispatchMouseEvent(IdeEventQueue.java:831)
    at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:753)
    at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$5(IdeEventQueue.java:437)
    at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:787)
    at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$6(IdeEventQueue.java:436)
    at com.intellij.openapi.application.TransactionGuardImpl.performActivity(TransactionGuardImpl.java:113)
    at com.intellij.ide.IdeEventQueue.performActivity(IdeEventQueue.java:615)
    at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$7(IdeEventQueue.java:434)
    at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:838)
    at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:480)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:207)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:128)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:117)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:105)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:92)
Caused by: com.intellij.diagnostic.PluginException: Cannot create class com.airsaid.localization.translate.services.TranslatorService (classloader=PluginClassLoader(plugin=PluginDescriptor(name=AndroidLocalize, id=com.github.airsaid.androidlocalize, descriptorPath=plugin.xml, path=~\AppData\Local\JetBrains\Toolbox\apps\AndroidStudio\ch-1\223.8617.56.2231.9687552.plugins\AndroidLocalizePlugin-3.0.0.jar, version=3.0.0, package=null, isBundled=false), packagePrefix=null, instanceId=137, state=active))
    at com.intellij.serviceContainer.ComponentManagerImpl.instantiateClass(ComponentManagerImpl.kt:989)
    at com.intellij.serviceContainer.ComponentManagerImpl.createLightService(ComponentManagerImpl.kt:929)
    at com.intellij.serviceContainer.ComponentManagerImpl.getOrCreateLightService(ComponentManagerImpl.kt:733)
    at com.intellij.serviceContainer.ComponentManagerImpl.doGetService(ComponentManagerImpl.kt:685)
    at com.intellij.serviceContainer.ComponentManagerImpl.getService(ComponentManagerImpl.kt:629)
    at com.intellij.openapi.components.ServiceManager.getService(ServiceManager.java:19)
    at com.airsaid.localization.translate.services.TranslatorService.getInstance(TranslatorService.java:73)
    at com.airsaid.localization.config.SettingsState.<init>(SettingsState.java:51)
    at com.intellij.serviceContainer.ComponentManagerImpl.instantiateClass(ComponentManagerImpl.kt:950)
    ... 63 more
Caused by: java.util.ServiceConfigurationError: com.airsaid.localization.translate.AbstractTranslator: Provider com.airsaid.localization.translate.impl.ali.AliTranslator could not be instantiated
    at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:586)
    at java.base/java.util.ServiceLoader$ProviderImpl.newInstance(ServiceLoader.java:813)
    at java.base/java.util.ServiceLoader$ProviderImpl.get(ServiceLoader.java:729)
    at java.base/java.util.ServiceLoader$3.next(ServiceLoader.java:1403)
    at com.airsaid.localization.translate.services.TranslatorService.<init>(TranslatorService.java:60)
    at com.intellij.serviceContainer.ComponentManagerImpl.instantiateClass(ComponentManagerImpl.kt:950)
    ... 71 more
Caused by: java.lang.NoClassDefFoundError: com/aliyun/teaopenapi/models/Config
    at com.airsaid.localization.translate.impl.ali.AliTranslator.<init>(AliTranslator.java:48)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
    at java.base/java.util.ServiceLoader$ProviderImpl.newInstance(ServiceLoader.java:789)
    ... 75 more
Caused by: java.lang.ClassNotFoundException: com.aliyun.teaopenapi.models.Config PluginClassLoader(plugin=PluginDescriptor(name=AndroidLocalize, id=com.github.airsaid.androidlocalize, descriptorPath=plugin.xml, path=~\AppData\Local\JetBrains\Toolbox\apps\AndroidStudio\ch-1\223.8617.56.2231.9687552.plugins\AndroidLocalizePlugin-3.0.0.jar, version=3.0.0, package=null, isBundled=false), packagePrefix=null, instanceId=137, state=active)
    ... 82 more
Airsaid commented 1 year ago

I don't have that problem on my end, it looks like the corresponding class is not downloaded from the maven repository.

I ran it and found that the OpenAI Icon image is a bit too big, so I will replace it.

Airsaid commented 1 year ago

I made some changes, can you see if it's OK now?

rbenza commented 1 year ago

I made some changes, can you see if it's OK now?

Yes, looks fine to me. Fixed my issue, was loading the incomplete JAR file (without external dependencies) instead of the complete ZIP file inside build\distributions

Also fixed incorrect request body formatting. I've added a system role for ChatGPT to increase the translation quality. But I do experience this issue now:

Caused by: java.net.SocketTimeoutException: Read timed out
    at java.base/java.net.SocketInputStream.socketRead0(Native Method)
    at java.base/java.net.SocketInputStream.socketRead(SocketInputStream.java:115)
    at java.base/java.net.SocketInputStream.read(SocketInputStream.java:168)
    at java.base/java.net.SocketInputStream.read(SocketInputStream.java:140)
    at java.base/sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:478)
    at java.base/sun.security.ssl.SSLSocketInputRecord.readHeader(SSLSocketInputRecord.java:472)
    at java.base/sun.security.ssl.SSLSocketInputRecord.bytesInCompletePacket(SSLSocketInputRecord.java:70)
    at java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1454)
    at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:1065)
    at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:252)
    at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:292)
    at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:351)
    at java.base/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:754)
    at java.base/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:689)
    at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1615)
    at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1520)
    at java.base/sun.net.www.protocol.http.HttpURLConnection.getHeaderField(HttpURLConnection.java:3103)
    at java.base/java.net.URLConnection.getHeaderFieldLong(URLConnection.java:636)
    at java.base/java.net.URLConnection.getContentLengthLong(URLConnection.java:508)
    at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getContentLengthLong(HttpsURLConnectionImpl.java:398)
    at com.intellij.util.io.HttpRequests$RequestImpl.doReadBytes(HttpRequests.java:435)
    ... 31 more

@Airsaid is it correct to use connection.setConnectTimeout(0); on the requestBuilder to prevent this issue? Does your library wait to move to the next string value until the server response is received? Regardless on how long it takes?

Airsaid commented 1 year ago

It's great, I ran the latest version and it works fine.

Using the infinite timeout I don't think is very good, it does block the currently translated string.

The timeout issue is usually a problem with the user's network or translation API service, and there is nothing we can do on the plugin side to fix it. Maybe we could set a slightly longer timeout instead of an infinite timeout?

Also, what is the approximate increase in accuracy after adding system roles to ChatGPT? Because currently, every translation will carry the system role command, which will significantly increase the token usage.

rbenza commented 1 year ago

It's great, I ran the latest version and it works fine.

Using the infinite timeout I don't think is very good, it does block the currently translated string.

The timeout issue is usually a problem with the user's network or translation API service, and there is nothing we can do on the plugin side to fix it. Maybe we could set a slightly longer timeout instead of an infinite timeout?

Also, what is the approximate increase in accuracy after adding system roles to ChatGPT? Because currently, every translation will carry the system role command, which will significantly increase the token usage.

Think the ChatGPT system role is important to get the best result. It's around 100 tokens so only 0.0002 dollar per translated string. If you translate 500 strings this would be 0.10 dollar. That's acceptable to me. Maybe we can shorten it a bit.

However I still often get timeout errors. Can not be my connection since i'm on high end 1gbit/s connection. One thing we should add is at least return all the currently translated strings, before the error happens.. Since now everytime an error occurs all the previously translated strings are not being returned. So you waste money each time a error happens since OpenAI is a paid service.

Another thing that would be great is make the notification dynamic. So 'translating to Italian... (string 2 of 423)' that keeps updating so you know the current progress.

Airsaid commented 1 year ago

It's great, I ran the latest version and it works fine. Using the infinite timeout I don't think is very good, it does block the currently translated string. The timeout issue is usually a problem with the user's network or translation API service, and there is nothing we can do on the plugin side to fix it. Maybe we could set a slightly longer timeout instead of an infinite timeout? Also, what is the approximate increase in accuracy after adding system roles to ChatGPT? Because currently, every translation will carry the system role command, which will significantly increase the token usage.

Think the ChatGPT system role is important to get the best result. It's around 100 tokens so only 0.0002 dollar per translated string. If you translate 500 strings this would be 0.10 dollar. That's acceptable to me. Maybe we can shorten it a bit.

However I still often get timeout errors. Can not be my connection since i'm on high end 1gbit/s connection. One thing we should add is at least return all the currently translated strings, before the error happens.. Since now everytime an error occurs all the previously translated strings are not being returned. So you waste money each time a error happens since OpenAI is a paid service.

Another thing that would be great is make the notification dynamic. So 'translating to Italian... (string 2 of 423)' that keeps updating so you know the current progress.

I've seen the last few commits shorten the command.

The problem of interruption after an error is indeed a big optimization point, I will optimize the logic of this piece in the next few days.

Also about optimizing the notification content, this is also a good suggestion, I should be able to optimize it in the next version.

I don't think this PR is much of a problem anymore. Is there anything else to optimize on your end? If not, I think it can be merged.

rbenza commented 1 year ago

Yes, you can merge the PR. But I would only do a release if you fix the issue that the translated (and paid for) strings are returned when an error occurs.

Airsaid commented 1 year ago

I merged it, thanks again for your contribution!