├── src ├── test │ ├── resources │ │ └── psiutils │ │ │ └── dart │ │ │ ├── empty.dart │ │ │ ├── non_dart_file.txt │ │ │ ├── additive_expr_3.dart │ │ │ ├── additive_expr_2.dart │ │ │ ├── const_modifier_finder │ │ │ ├── var_declaration_1.dart │ │ │ ├── list_declaration_1.dart │ │ │ ├── class_declaration_1.dart │ │ │ └── class_declaration_2.dart │ │ │ ├── example2.dart │ │ │ ├── example5.dart │ │ │ ├── example6.dart │ │ │ ├── filter │ │ │ └── import_string_literal.dart │ │ │ ├── example1.dart │ │ │ ├── example3.dart │ │ │ ├── example4.dart │ │ │ ├── with_comments.dart │ │ │ └── additive_expr_1.dart │ ├── testData │ │ └── rename │ │ │ ├── foo.xml │ │ │ └── foo_after.xml │ └── kotlin │ │ ├── features │ │ ├── translations │ │ │ ├── domain │ │ │ │ └── mapper │ │ │ │ │ └── ArbFilenameParserTest.kt │ │ │ └── infrastructure │ │ │ │ └── service │ │ │ │ └── JsonChunkMergerTest.kt │ │ ├── psiutils │ │ │ ├── filter │ │ │ │ └── ImportStatementFilterDartStringTest.kt │ │ │ ├── LiteralInContextFinderTest.kt │ │ │ ├── DartAdditiveExpressionExtractorTest.kt │ │ │ └── DartConstModifierFinderTest.kt │ │ └── shared │ │ │ └── infrastructure │ │ │ ├── model │ │ │ └── UserSettingsTest.kt │ │ │ └── utils │ │ │ └── JsonUtilsTest.kt │ │ └── com │ │ └── github │ │ └── keeyzar │ │ └── gpthelper │ │ └── MyPluginTest.kt └── main │ ├── kotlin │ ├── de │ │ └── keeyzar │ │ │ └── gpthelper │ │ │ └── gpthelper │ │ │ ├── features │ │ │ ├── translations │ │ │ │ ├── infrastructure │ │ │ │ │ ├── repository │ │ │ │ │ │ ├── CurrentProjectProvider.kt │ │ │ │ │ │ └── IdeaTranslationCredentialsServiceRepository.kt │ │ │ │ │ ├── dto │ │ │ │ │ │ └── GPTArbTranslationResponse.kt │ │ │ │ │ ├── model │ │ │ │ │ │ └── TranslateWholeFileContext.kt │ │ │ │ │ ├── service │ │ │ │ │ │ ├── LastStatementProviderForFlutterArbTranslation.kt │ │ │ │ │ │ ├── ARBTemplateService.kt │ │ │ │ │ │ ├── TranslationProgressChangeNotifier.kt │ │ │ │ │ │ ├── ImmediateTranslationService.kt │ │ │ │ │ │ ├── IdeaTranslationProgressBus.kt │ │ │ │ │ │ ├── FlutterGenCommandProcessService.kt │ │ │ │ │ │ ├── JsonChunkMerger.kt │ │ │ │ │ │ ├── IdeaGatherFileTranslationContext.kt │ │ │ │ │ │ ├── ContextProvider.kt │ │ │ │ │ │ ├── JsonFileChunker.kt │ │ │ │ │ │ └── ArbContentModificationService.kt │ │ │ │ │ ├── client │ │ │ │ │ │ ├── TranslateKeyTaskAmountCalculator.kt │ │ │ │ │ │ ├── GptModelProviderImpl.kt │ │ │ │ │ │ ├── OpenAIClientConnectionTester.kt │ │ │ │ │ │ └── DispatcherConfiguration.kt │ │ │ │ │ ├── mapper │ │ │ │ │ │ └── UserSettingsMapper.kt │ │ │ │ │ ├── parser │ │ │ │ │ │ ├── GPTARBResponseParser.kt │ │ │ │ │ │ └── ARBFileContentParser.kt │ │ │ │ │ └── configuration │ │ │ │ │ │ └── LLMConfigProvider.kt │ │ │ │ ├── presentation │ │ │ │ │ ├── pojo │ │ │ │ │ │ ├── package-info.java │ │ │ │ │ │ └── TranslationDialogUserInput.kt │ │ │ │ │ ├── exception │ │ │ │ │ │ └── PsiElementException.kt │ │ │ │ │ ├── service │ │ │ │ │ │ └── ArbFormatTranslationFileContentService.kt │ │ │ │ │ └── validation │ │ │ │ │ │ ├── TranslationClientSettingsValidator.kt │ │ │ │ │ │ └── FlutterIntlValidator.kt │ │ │ │ └── domain │ │ │ │ │ ├── client │ │ │ │ │ ├── GPTModelProvider.kt │ │ │ │ │ ├── ClientConnectionTester.kt │ │ │ │ │ ├── BatchPartialTranslationResponse.kt │ │ │ │ │ ├── BatchClientTranslationRequest.kt │ │ │ │ │ ├── TaskAmountCalculator.kt │ │ │ │ │ ├── ClientTranslationRequest.kt │ │ │ │ │ ├── PartialTranslationResponse.kt │ │ │ │ │ ├── SingleTranslationRequestClient.kt │ │ │ │ │ └── DDDTranslationRequestClient.kt │ │ │ │ │ ├── entity │ │ │ │ │ ├── TranslationProgress.kt │ │ │ │ │ ├── ARBFileContent.kt │ │ │ │ │ ├── Translation.kt │ │ │ │ │ ├── UserAddNewLanguageRequest.kt │ │ │ │ │ ├── ChangeTranslationContext.kt │ │ │ │ │ ├── CurrentFileModificationContext.kt │ │ │ │ │ ├── UserTranslationResponse.kt │ │ │ │ │ ├── UserTranslateStringRequest.kt │ │ │ │ │ ├── UserTranslationRequest.kt │ │ │ │ │ ├── FileToTranslate.kt │ │ │ │ │ ├── SimpleTranslationEntry.kt │ │ │ │ │ ├── Language.kt │ │ │ │ │ ├── UserTranslationInput.kt │ │ │ │ │ ├── TranslateKeyContext.kt │ │ │ │ │ └── TranslationContext.kt │ │ │ │ │ ├── service │ │ │ │ │ ├── TranslateResponseHandler.kt │ │ │ │ │ ├── TranslationErrorProcessHandler.kt │ │ │ │ │ ├── ConsoleService.kt │ │ │ │ │ ├── TranslationProgressBus.kt │ │ │ │ │ ├── ExternalTranslationProcessService.kt │ │ │ │ │ ├── FormatTranslationFileContentService.kt │ │ │ │ │ ├── GatherTranslationContextService.kt │ │ │ │ │ ├── VerifyTranslationSettingsService.kt │ │ │ │ │ ├── ContentModificationService.kt │ │ │ │ │ ├── CurrentFileModificationService.kt │ │ │ │ │ ├── GatherUserInputService.kt │ │ │ │ │ ├── TranslationTriggeredHooks.kt │ │ │ │ │ ├── IdeaTranslationErrorProcessHandlerImpl.kt │ │ │ │ │ ├── TranslateKeyTranslationTriggeredHooks.kt │ │ │ │ │ └── TranslationPreprocessor.kt │ │ │ │ │ ├── exceptions │ │ │ │ │ ├── ReplacementOfTranslationFailedException.kt │ │ │ │ │ ├── BasePathSettingMissing.kt │ │ │ │ │ ├── FilePrefixSettingMissing.kt │ │ │ │ │ ├── GatherContextException.kt │ │ │ │ │ ├── UserSettingsException.kt │ │ │ │ │ ├── TranslationRequestException.kt │ │ │ │ │ ├── UserSettingsCorruptException.kt │ │ │ │ │ ├── UserSettingsMissingException.kt │ │ │ │ │ ├── TranslationClientConnectionException.kt │ │ │ │ │ ├── translationdialogdata │ │ │ │ │ │ ├── TranslationDialogDataException.kt │ │ │ │ │ │ ├── TranslationDialogDataCorruptException.kt │ │ │ │ │ │ └── TranslationDialogDataMissingException.kt │ │ │ │ │ ├── NoTranslationFilesException.kt │ │ │ │ │ ├── ExecuteGenCommandProcessServiceException.kt │ │ │ │ │ ├── CurrentFileModificationException.kt │ │ │ │ │ ├── TranslationFileNotFound.kt │ │ │ │ │ └── TranslationFileModificationException.kt │ │ │ │ │ ├── repository │ │ │ │ │ ├── TranslationCredentialsServiceRepository.kt │ │ │ │ │ ├── UserSettingsRepository.kt │ │ │ │ │ ├── UserTranslationInputRepository.kt │ │ │ │ │ └── TranslationFileRepository.kt │ │ │ │ │ ├── mapper │ │ │ │ │ └── TranslationRequestMapper.kt │ │ │ │ │ └── parser │ │ │ │ │ ├── UserTranslationInputParser.kt │ │ │ │ │ └── ArbFilenameParser.kt │ │ │ ├── psiutils │ │ │ │ ├── arb │ │ │ │ │ ├── ArbEntry.kt │ │ │ │ │ └── StringArrayContent.kt │ │ │ │ ├── filter │ │ │ │ │ ├── DartStringLiteralFilter.kt │ │ │ │ │ └── ImportStatementFilterDartString.kt │ │ │ │ ├── PsiElementIdGenerator.kt │ │ │ │ ├── DartAdditiveExpressionExtractor.kt │ │ │ │ └── LiteralInContextFinder.kt │ │ │ ├── filetranslation │ │ │ │ ├── domain │ │ │ │ │ ├── entity │ │ │ │ │ │ ├── FileTranslationContext.kt │ │ │ │ │ │ ├── Context.kt │ │ │ │ │ │ └── TranslateFileContext.kt │ │ │ │ │ ├── service │ │ │ │ │ │ ├── FileTranslationFinished.kt │ │ │ │ │ │ ├── FinishedFileTranslationHandler.kt │ │ │ │ │ │ ├── GatherFileTranslationContext.kt │ │ │ │ │ │ └── PartialFileResponseHandler.kt │ │ │ │ │ ├── client │ │ │ │ │ │ ├── PartialFileTranslationResponse.kt │ │ │ │ │ │ └── FileTranslationRequest.kt │ │ │ │ │ └── factories │ │ │ │ │ │ └── TranslationRequestFactory.kt │ │ │ │ ├── infrastructure │ │ │ │ │ ├── service │ │ │ │ │ │ ├── TargetLanguageProvider.kt │ │ │ │ │ │ ├── ArbFlutterPartialFileTranslationResponseHandler.kt │ │ │ │ │ │ ├── FlutterArbTranslateFileFinished.kt │ │ │ │ │ │ └── ArbFileContentModificationService.kt │ │ │ │ │ ├── mapper │ │ │ │ │ │ └── FileTranslationRequestMapper.kt │ │ │ │ │ └── factories │ │ │ │ │ │ └── TranslationRequestFactoryImpl.kt │ │ │ │ └── presentation │ │ │ │ │ ├── service │ │ │ │ │ └── IdeaTargetLanguageProvider.kt │ │ │ │ │ └── widgets │ │ │ │ │ └── TargetLanguageDialog.kt │ │ │ ├── autofilefixer │ │ │ │ ├── domain │ │ │ │ │ ├── entity │ │ │ │ │ │ ├── SingleLiteral.kt │ │ │ │ │ │ ├── FileBestGuessContext.kt │ │ │ │ │ │ ├── PreFilterResponse.kt │ │ │ │ │ │ ├── PreFilterRequest.kt │ │ │ │ │ │ └── MultiKeyTranslationContext.kt │ │ │ │ │ ├── client │ │ │ │ │ │ ├── BestGuessRequest.kt │ │ │ │ │ │ ├── BestGuessResponse.kt │ │ │ │ │ │ ├── BestGuessResponseEntry.kt │ │ │ │ │ │ ├── BestGuessL10nClient.kt │ │ │ │ │ │ └── PreFilterClient.kt │ │ │ │ │ ├── service │ │ │ │ │ │ ├── WaitingIndicatorService.kt │ │ │ │ │ │ ├── MultiKeyTranslationTaskSizeEstimator.kt │ │ │ │ │ │ ├── GatherBestGuessContext.kt │ │ │ │ │ │ ├── PreFilterRequestBuilder.kt │ │ │ │ │ │ ├── GuessAdaptionService.kt │ │ │ │ │ │ └── IdeaGatherBestGuessContext.kt │ │ │ │ │ └── exception │ │ │ │ │ │ └── BestGuessClientException.kt │ │ │ │ ├── infrastructure │ │ │ │ │ ├── search │ │ │ │ │ │ ├── Suggestion.kt │ │ │ │ │ │ ├── ArbIndexEntry.kt │ │ │ │ │ │ ├── ArbFileContentProvider.kt │ │ │ │ │ │ └── ArbIndexer.kt │ │ │ │ │ ├── model │ │ │ │ │ │ └── AutoLocalizeContext.kt │ │ │ │ │ ├── service │ │ │ │ │ │ ├── OpenAIMultiKeyTranslationTaskSizeEstimator.kt │ │ │ │ │ │ ├── PsiElementIdReferenceProvider.kt │ │ │ │ │ │ └── IdeaPreFilterRequestBuilder.kt │ │ │ │ │ └── parser │ │ │ │ │ │ └── BestGuessOpenAIResponseParser.kt │ │ │ │ └── presentation │ │ │ │ │ ├── dto │ │ │ │ │ └── BestGuessWithPsiReference.kt │ │ │ │ │ ├── actions │ │ │ │ │ ├── AutoLocalizeDirectory.kt │ │ │ │ │ └── DartStringRequiredPattern.kt │ │ │ │ │ ├── widgets │ │ │ │ │ └── RetryFailedTranslationsDialog.kt │ │ │ │ │ └── service │ │ │ │ │ └── IdeaWaitingIndicatorService.kt │ │ │ ├── review │ │ │ │ ├── domain │ │ │ │ │ ├── service │ │ │ │ │ │ ├── OpenPageService.kt │ │ │ │ │ │ ├── AskUserForReviewService.kt │ │ │ │ │ │ └── IdeaAskUserForReviewService.kt │ │ │ │ │ ├── entity │ │ │ │ │ │ ├── AskUserForReviewResult.kt │ │ │ │ │ │ └── ReviewSettings.kt │ │ │ │ │ ├── repository │ │ │ │ │ │ └── ReviewRepository.kt │ │ │ │ │ └── config │ │ │ │ │ │ └── ReviewConfig.kt │ │ │ │ └── infrastructure │ │ │ │ │ ├── mapper │ │ │ │ │ └── ReviewSettingsMapper.kt │ │ │ │ │ ├── model │ │ │ │ │ └── ReviewSettingsModel.kt │ │ │ │ │ └── service │ │ │ │ │ └── IdeaOpenPageService.kt │ │ │ ├── changetranslation │ │ │ │ ├── package-info.java │ │ │ │ └── domain │ │ │ │ │ └── controller │ │ │ │ │ └── ChangeTranslationController.kt │ │ │ ├── shared │ │ │ │ ├── domain │ │ │ │ │ ├── service │ │ │ │ │ │ ├── InstallFileProvider.kt │ │ │ │ │ │ ├── PathProvider.kt │ │ │ │ │ │ └── ThreadingService.kt │ │ │ │ │ └── exception │ │ │ │ │ │ ├── ProgrammerException.kt │ │ │ │ │ │ └── GPTHelperBaseException.kt │ │ │ │ ├── presentation │ │ │ │ │ ├── mapper │ │ │ │ │ │ └── UserSettingsDTOMapper.kt │ │ │ │ │ ├── actions │ │ │ │ │ │ └── ProjectAwareAction.kt │ │ │ │ │ ├── dto │ │ │ │ │ │ └── UserSettingsDTO.kt │ │ │ │ │ └── Initializer.kt │ │ │ │ └── infrastructure │ │ │ │ │ ├── service │ │ │ │ │ └── IdeaPathProvider.kt │ │ │ │ │ ├── utils │ │ │ │ │ └── ObjectMapperProvider.kt │ │ │ │ │ └── model │ │ │ │ │ └── UserSettings.kt │ │ │ ├── flutter_intl │ │ │ │ ├── domain │ │ │ │ │ ├── exceptions │ │ │ │ │ │ ├── FlutterIntlFileNotFound.kt │ │ │ │ │ │ └── FlutterIntlFileParseException.kt │ │ │ │ │ ├── repository │ │ │ │ │ │ └── FlutterIntlSettingsRepository.kt │ │ │ │ │ └── entity │ │ │ │ │ │ └── FlutterIntlSettings.kt │ │ │ │ └── infrastructure │ │ │ │ │ ├── repository │ │ │ │ │ └── FlutterFileRepository.kt │ │ │ │ │ └── service │ │ │ │ │ └── ArbFilesService.kt │ │ │ ├── missingtranslations │ │ │ │ ├── domain │ │ │ │ │ ├── entity │ │ │ │ │ │ ├── ExistingTranslation.kt │ │ │ │ │ │ ├── MissingTranslation.kt │ │ │ │ │ │ ├── MissingTranslationAndExistingTranslation.kt │ │ │ │ │ │ ├── MissingTranslationFilteredTargetTranslation.kt │ │ │ │ │ │ └── MissingTranslationContext.kt │ │ │ │ │ ├── repository │ │ │ │ │ │ └── ExistingTranslationRepository.kt │ │ │ │ │ └── service │ │ │ │ │ │ ├── MissingTranslationInputService.kt │ │ │ │ │ │ └── MissingTranslationCollectionService.kt │ │ │ │ └── infrastructure │ │ │ │ │ ├── service │ │ │ │ │ ├── MissingTranslationInputServiceIdea.kt │ │ │ │ │ └── MissingTranslationCollectionServiceIdea.kt │ │ │ │ │ └── repository │ │ │ │ │ └── ExistingTranslationRepositoryIdea.kt │ │ │ └── setup │ │ │ │ ├── domain │ │ │ │ ├── service │ │ │ │ │ ├── YamlModificationService.kt │ │ │ │ │ ├── UserInstallDialogs.kt │ │ │ │ │ └── AppReferenceProvider.kt │ │ │ │ └── model │ │ │ │ │ └── SetupProcessModel.kt │ │ │ │ └── presentation │ │ │ │ └── actions │ │ │ │ └── SetupIntlAction.kt │ │ │ ├── KoinInstances.kt │ │ │ └── project │ │ │ ├── ProjectKoinService.kt │ │ │ └── ProjectSetupStateService.kt │ └── com │ │ └── github │ │ └── keeyzar │ │ └── gpthelper │ │ └── MyBundle.kt │ ├── resources │ ├── messages │ │ └── MyBundle.properties │ └── intentionDescriptions │ │ ├── ArbIntentionAction │ │ └── description.html │ │ └── GenerateArbIntentionAction │ │ └── description.html │ └── java │ └── de │ └── keeyzar │ └── gpthelper │ └── gpthelper │ └── features │ └── flutterarb │ └── presentation │ └── handler │ ├── package-info.java │ └── ArbFileType.java ├── .gitignore ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── settings.gradle.kts ├── codecov.yml ├── qodana.yml ├── .idea └── gradle.xml ├── .run ├── Run Tests.run.xml ├── Run Plugin.run.xml └── Run Verifications.run.xml └── gradle.properties /src/test/resources/psiutils/dart/empty.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | } 3 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/non_dart_file.txt: -------------------------------------------------------------------------------- 1 | hello world 2 | -------------------------------------------------------------------------------- /src/test/testData/rename/foo.xml: -------------------------------------------------------------------------------- 1 | 2 | 1>Foo 3 | 4 | -------------------------------------------------------------------------------- /src/test/testData/rename/foo_after.xml: -------------------------------------------------------------------------------- 1 | 2 | Foo 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .gradle 3 | .idea 4 | .intellijPlatform 5 | .kotlin 6 | .qodana 7 | build 8 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/additive_expr_3.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | void main() { 4 | } 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keeyzar/flutterintl/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/repository/CurrentProjectProvider.kt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/additive_expr_2.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | String nice = 'nice ' + 'world ' + 'new'; 3 | } 4 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/const_modifier_finder/var_declaration_1.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | const x = "Hello world"; 3 | } 4 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/example2.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | var list = ['apple', 'banana', 'cherry']; 3 | print(list); 4 | } 5 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/example5.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | String complexString = 'Hello ' + 3.toString() + ' World!'; 3 | } 4 | -------------------------------------------------------------------------------- /src/main/resources/messages/MyBundle.properties: -------------------------------------------------------------------------------- 1 | projectService=Project service: {0} 2 | randomLabel=The random number is: {0} 3 | shuffle=Shuffle 4 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/example6.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | String complexString = 'Hello ' + 3.toString() + ' World!'; 3 | print("Omg") 4 | } 5 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" 3 | } 4 | 5 | rootProject.name = "gpt-helper" 6 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/const_modifier_finder/list_declaration_1.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | const x = [ 3 | "nice", 4 | "nice2", 5 | ]; 6 | } 7 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/filter/import_string_literal.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | void main() { 4 | String empty = 'empty' + 3.toString() + 'but it works'; 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/example1.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | String hello = 'Hello, World!'; 3 | String example = "This is an example"; 4 | print(hello); 5 | print(example); 6 | } 7 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/const_modifier_finder/class_declaration_1.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | const Hello("Hello world"); 3 | } 4 | 5 | class Hello{ 6 | final String text; 7 | const Hello(this.text); 8 | } 9 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | informational: true 6 | threshold: 0% 7 | base: auto 8 | patch: 9 | default: 10 | informational: true 11 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/psiutils/arb/ArbEntry.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.psiutils.arb 2 | 3 | data class ArbEntry(val key: String, val value: String, var description: String = "") -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/example3.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | String empty = ''; 3 | String singleWord = 'word'; 4 | String sentence = "This is a sentence."; 5 | print(empty); 6 | print(singleWord); 7 | print(sentence); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/presentation/pojo/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * some general POJOs required by presentation layer 3 | */ 4 | package de.keeyzar.gpthelper.gpthelper.features.translations.presentation.pojo; 5 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/example4.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | String complexString = 'Hello, World!'; 3 | String nice = "This is an example"; 4 | Hello("nice"); 5 | } 6 | 7 | class Hello { 8 | String name; 9 | Hello(this.name); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/domain/entity/FileTranslationContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.entity 2 | 3 | interface FileTranslationContext : Context { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/with_comments.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | //String empty = ''; 3 | //String singleWord = 'word'; 4 | //String sentence = "This is a sentence."; 5 | // print(empty); 6 | // print(singleWord); 7 | // print(sentence); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/entity/SingleLiteral.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity 2 | 3 | class SingleLiteral( 4 | val id: String, 5 | val context: String, 6 | ) { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/review/domain/service/OpenPageService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.review.domain.service 2 | 3 | import java.net.URI 4 | 5 | fun interface OpenPageService { 6 | fun openPage(link: URI) 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/client/GPTModelProvider.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.client 2 | 3 | abstract class GPTModelProvider { 4 | abstract fun getAllModels(): List 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/domain/service/FileTranslationFinished.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.service 2 | 3 | interface FileTranslationFinished { 4 | fun fileTranslationFinished() 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/TranslationProgress.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | data class TranslationProgress(val taskAmount: Int, val currentTask: Int, val taskId: String) 4 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/additive_expr_1.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | String cool = "3"; 3 | var omg = Nice(); 4 | String complexString = """ 5 | hello ${cool} world $cool ${omg.another} 6 | """; 7 | } 8 | class Nice { 9 | String another = "lol"; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/ARBFileContent.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | data class ARBFileContent( 4 | val lang: String, 5 | val content: String, 6 | ) { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/TranslateResponseHandler.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | /** 4 | * Handles translation responses 5 | */ 6 | class TranslateResponseHandler { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/changetranslation/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Allows retranslation of an existing key, either from the arb file or from within the ide on an existing arb entry 3 | */ 4 | package de.keeyzar.gpthelper.gpthelper.features.changetranslation; -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/entity/FileBestGuessContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity 2 | 3 | data class FileBestGuessContext( 4 | val filename: String, 5 | val literals: List 6 | ) 7 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/TranslationErrorProcessHandler.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | fun interface TranslationErrorProcessHandler { 4 | fun displayErrorToUser(e: Throwable) 5 | } 6 | -------------------------------------------------------------------------------- /src/main/resources/intentionDescriptions/ArbIntentionAction/description.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Change a translation and translate for all other languages, too 4 |

5 | GPT will ask you for a new value, and translate all other selected languages, too. 6 |

7 | 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/changetranslation/domain/controller/ChangeTranslationController.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.changetranslation.domain.controller 2 | 3 | class ChangeTranslationController( 4 | ) { 5 | fun changeTranslation() { 6 | 7 | } 8 | } -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/domain/client/PartialFileTranslationResponse.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.client 2 | 3 | data class PartialFileTranslationResponse( 4 | val entry: Map = mutableMapOf(), 5 | ) 6 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/psiutils/filter/DartStringLiteralFilter.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.psiutils.filter 2 | 3 | import com.intellij.psi.PsiElement 4 | 5 | interface DartStringLiteralFilter { 6 | fun filter(psiElement: PsiElement): Boolean 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/infrastructure/search/Suggestion.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.infrastructure.search 2 | 3 | data class Suggestion( 4 | val key: String, 5 | val value: String, 6 | val score: Double 7 | ) 8 | 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/test/resources/psiutils/dart/const_modifier_finder/class_declaration_2.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | const Outer(Hello("Hello world")); 3 | } 4 | 5 | class Hello{ 6 | final String text; 7 | const Hello(this.text); 8 | } 9 | 10 | class Outer{ 11 | final Hello hello; 12 | const Outer(this.hello); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/Translation.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | /** 4 | * represents a translation for a single language 5 | */ 6 | data class Translation(val lang: Language, val entry: SimpleTranslationEntry) { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/ReplacementOfTranslationFailedException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions 2 | 3 | class ReplacementOfTranslationFailedException(message: String) : TranslationFileModificationException(message) { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/dto/GPTArbTranslationResponse.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.dto 2 | 3 | data class GPTArbTranslationResponse( 4 | val content: Map, 5 | ) { 6 | //toString, just print the de map 7 | } 8 | -------------------------------------------------------------------------------- /qodana.yml: -------------------------------------------------------------------------------- 1 | # Qodana configuration: 2 | # https://www.jetbrains.com/help/qodana/qodana-yaml.html 3 | 4 | version: "1.0" 5 | linter: jetbrains/qodana-jvm-community:2024.3 6 | projectJDK: "21" 7 | profile: 8 | name: qodana.recommended 9 | some: 10 | more: nice 11 | exclude: 12 | - name: All 13 | paths: 14 | - .qodana 15 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/psiutils/arb/StringArrayContent.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.psiutils.arb 2 | 3 | /** 4 | * Often used in arb intl files 5 | * e.g. "de" -> "key1", "key2" 6 | */ 7 | data class StringArrayContent( 8 | val key: String, 9 | val values: List, 10 | ) -------------------------------------------------------------------------------- /src/main/java/de/keeyzar/gpthelper/gpthelper/features/flutterarb/presentation/handler/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * we need to parse .arb files and also untranslated_messages.txt (which is json) to get keys and values which are not (yet) translated or must be updated 3 | */ 4 | package de.keeyzar.gpthelper.gpthelper.features.flutterarb.presentation.handler; -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/review/domain/entity/AskUserForReviewResult.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.review.domain.entity 2 | 3 | data class AskUserForReviewResult( 4 | val closedWithReview: Boolean, 5 | val closedWithShouldAskLater: Boolean, 6 | val closedWithDontAskAgain: Boolean, 7 | ) 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/UserAddNewLanguageRequest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | data class UserAddNewLanguageRequest( 4 | val oldFileContent: String, 5 | // format is either en_US or en 6 | val targetLanguage: String, 7 | ) 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/shared/domain/service/InstallFileProvider.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.shared.domain.service 2 | 3 | interface InstallFileProvider { 4 | fun fileExists(path: String): Boolean 5 | fun readFile(path: String): String? 6 | fun writeFile(path: String, content: String) 7 | } -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/infrastructure/search/ArbIndexEntry.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.infrastructure.search 2 | 3 | data class ArbIndexEntry( 4 | val key: String, 5 | val value: String, 6 | val normalizedValue: String, 7 | val trigrams: Set 8 | ) 9 | 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/client/BestGuessRequest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.client 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity.FileBestGuessContext 4 | 5 | data class BestGuessRequest( 6 | val context: FileBestGuessContext, 7 | ) { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/repository/TranslationCredentialsServiceRepository.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository 2 | 3 | interface TranslationCredentialsServiceRepository { 4 | fun persistKey(key: String) 5 | fun getKey(): String? 6 | fun hasPassword(): Boolean 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/review/domain/service/AskUserForReviewService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.review.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.review.domain.entity.AskUserForReviewResult 4 | 5 | fun interface AskUserForReviewService { 6 | fun askUserForReview(): AskUserForReviewResult? 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/client/ClientConnectionTester.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.client 2 | 3 | /** 4 | * should be used to find out whether the client has a connection 5 | */ 6 | fun interface ClientConnectionTester { 7 | suspend fun testClientConnection(key: String): Throwable? 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/ConsoleService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | /** 4 | * 5 | */ 6 | interface ConsoleService { 7 | /** 8 | * opens console, executes command, closes console 9 | */ 10 | fun executeCommand(command: String) 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/shared/domain/service/PathProvider.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.shared.domain.service 2 | 3 | /** 4 | * Provides paths for the current project. 5 | */ 6 | interface PathProvider { 7 | /** 8 | * Returns the root path of the project. 9 | */ 10 | fun getRootPath(): String 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/infrastructure/service/TargetLanguageProvider.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.infrastructure.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 4 | 5 | fun interface TargetLanguageProvider { 6 | fun getTargetLanguage(): Language? 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/flutter_intl/domain/exceptions/FlutterIntlFileNotFound.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.flutter_intl.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | open class FlutterIntlFileNotFound(message: String) : GPTHelperBaseException(message) { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/client/BatchPartialTranslationResponse.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.client 2 | 3 | /** 4 | * Response containing multiple partial translations 5 | */ 6 | data class BatchPartialTranslationResponse( 7 | val responses: List 8 | ) 9 | 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/infrastructure/model/AutoLocalizeContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.infrastructure.model 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.psi.PsiFile 5 | 6 | data class AutoLocalizeContext( 7 | val project: Project, 8 | val baseFile: PsiFile, 9 | ) 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/ChangeTranslationContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | /** 4 | * contains information for changing a single translation 5 | */ 6 | class ChangeTranslationContext( 7 | var key: String, 8 | var value: String, 9 | var description: String, 10 | ) -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/TranslationProgressBus.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.TranslationProgress 4 | 5 | interface TranslationProgressBus { 6 | fun pushPercentage(translationProgress: TranslationProgress) 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/CurrentFileModificationContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | /** 4 | * what is required to modify the current file, i.e. fix imports, fix current statement 5 | */ 6 | data class CurrentFileModificationContext( 7 | val userTranslationInput: UserTranslationInput, 8 | ) 9 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/domain/entity/Context.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.entity 2 | 3 | /** 4 | * Context for the translation in general, 5 | * while metadata is some extra data you can use to push some additional data through the domain 6 | */ 7 | interface Context { 8 | fun getMetadata(): T 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/missingtranslations/domain/entity/ExistingTranslation.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.entity 2 | 3 | /** 4 | * the translation that is already present in the arb files 5 | */ 6 | class ExistingTranslation( 7 | val key: String, 8 | val value: String, 9 | val description: String?, 10 | ) { 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/model/TranslateWholeFileContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.model 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.psi.PsiFile 5 | 6 | class TranslateWholeFileContext( 7 | val baseFile: PsiFile, 8 | val project: Project, 9 | ) { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/review/domain/repository/ReviewRepository.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.review.domain.repository 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.review.domain.entity.ReviewSettings 4 | 5 | interface ReviewRepository { 6 | fun getReviewSettings(): ReviewSettings 7 | fun saveReviewSettings(reviewSettings: ReviewSettings) 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/domain/service/FinishedFileTranslationHandler.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.entity.TranslateFileContext 4 | 5 | fun interface FinishedFileTranslationHandler { 6 | fun finishedTranslation(context: TranslateFileContext) 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/UserTranslationResponse.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | /** 4 | * represents all translations for all files, because the original translation might be modified because 5 | * of wording etc. 6 | */ 7 | class UserTranslationResponse(var translations: List) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/ExternalTranslationProcessService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | /** 4 | * in flutter e.g. you need to trigger a process after modification, might not be necessary for different languages 5 | */ 6 | fun interface ExternalTranslationProcessService { 7 | fun postTranslationProcess() 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/service/WaitingIndicatorService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.service 2 | 3 | import java.util.* 4 | 5 | interface WaitingIndicatorService { 6 | fun startWaiting(uuid: UUID, title: String, description: String) 7 | fun stopWaiting() 8 | fun updateProgress(uuid: UUID, progressText: String) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/flutter_intl/domain/exceptions/FlutterIntlFileParseException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.flutter_intl.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | open class FlutterIntlFileParseException(message: String, cause: Throwable) : GPTHelperBaseException(message, cause) { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/client/BestGuessResponse.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.client 2 | 3 | /** 4 | * this is a simple Best Guess Response. 5 | * There is also some more complex l10n possibilities, but let's ignore them for now 6 | */ 7 | data class BestGuessResponse( 8 | val responseEntries: List 9 | ) 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/domain/client/FileTranslationRequest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.client 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 4 | 5 | data class FileTranslationRequest( 6 | val content: String, 7 | val targetLanguage: Language, 8 | val baseLanguage: Language, 9 | ) 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/setup/domain/service/YamlModificationService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.setup.domain.service 2 | 3 | interface YamlModificationService { 4 | fun addDependency(yamlContent: String, dependencyName: String, dependencyValue: Any): String 5 | fun addFlutterGenerate(yamlContent: String): String 6 | fun createL10nYaml(l10nConfigMap: Map): String 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/FormatTranslationFileContentService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.FileToTranslate 4 | 5 | fun interface FormatTranslationFileContentService { 6 | fun formatTranslationFileContent(fileToTranslate: FileToTranslate): FileToTranslate 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/UserTranslateStringRequest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | data class UserTranslateStringRequest( 4 | val desiredKey: String, 5 | val desiredValue: String, 6 | val desiredDescription: String, 7 | /** 8 | * format is either en_US or en 9 | */ 10 | val langsToTranslate: Set, 11 | ) 12 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/service/MultiKeyTranslationTaskSizeEstimator.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity.MultiKeyTranslationContext 4 | 5 | fun interface MultiKeyTranslationTaskSizeEstimator { 6 | fun estimateTaskSize(multiKeyTranslationContext: MultiKeyTranslationContext): Int 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/UserTranslationRequest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | data class UserTranslationRequest ( 4 | /** 5 | * the languages to translate to 6 | */ 7 | val targetLanguages: List, 8 | /** 9 | * the base translation to translate from 10 | */ 11 | val baseTranslation: Translation, 12 | ) { 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/presentation/pojo/TranslationDialogUserInput.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.presentation.pojo 2 | 3 | data class TranslationDialogUserInput( 4 | val translationsChecked: MutableMap, 5 | var desiredValue: String, 6 | var desiredKey: String, 7 | var desiredDescription: String, 8 | var translateNow: Boolean // NEU: Checkbox-Wert 9 | ) 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/missingtranslations/domain/entity/MissingTranslation.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.entity 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 4 | 5 | /** 6 | * e.g. "greeting" might be missing in "de, pl, fr" 7 | */ 8 | data class MissingTranslation( 9 | val key: String, 10 | var languagesMissing : List, 11 | ) 12 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/service/LastStatementProviderForFlutterArbTranslation.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service 2 | 3 | import com.intellij.psi.PsiElement 4 | 5 | /** 6 | * TODO this is an issue, I need to fix... I need some kind of map at least 7 | */ 8 | class LastStatementProviderForFlutterArbTranslation { 9 | var lastStatement: PsiElement? = null 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/presentation/dto/BestGuessWithPsiReference.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.presentation.dto 2 | 3 | import com.intellij.psi.PsiElement 4 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.client.BestGuessResponseEntry 5 | 6 | data class BestGuessWithPsiReference( 7 | val id: String, 8 | val psiElement: PsiElement, 9 | val bestGuess: BestGuessResponseEntry, 10 | ) { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/client/BestGuessResponseEntry.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.client 2 | 3 | /** 4 | * this is a simple Best Guess Response. 5 | * There is also some more complex l10n possibilities, but let's ignore them for now 6 | */ 7 | data class BestGuessResponseEntry( 8 | val id: String, 9 | val key: String, 10 | val description: String, 11 | val placeholder: Map? = null 12 | ) 13 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/entity/PreFilterResponse.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity 2 | 3 | /** 4 | * Response from pre-filtering indicating which literals should be translated 5 | */ 6 | data class PreFilterResponse( 7 | val results: List 8 | ) 9 | 10 | data class PreFilterResult( 11 | val id: String, 12 | val shouldTranslate: Boolean, 13 | val reason: String? = null 14 | ) 15 | 16 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/shared/domain/exception/ProgrammerException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | open class ProgrammerException : GPTHelperBaseException { 6 | constructor() : super() 7 | constructor(message: String?) : super(message) 8 | constructor(message: String?, cause: Throwable?) : super(message, cause) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/missingtranslations/domain/entity/MissingTranslationAndExistingTranslation.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.entity 2 | 3 | /** 4 | * contains the missing translations and the corresponding existing translation which the user wants to translate 5 | */ 6 | data class MissingTranslationAndExistingTranslation( 7 | val missingTranslation: MissingTranslation, 8 | var existingTranslation: ExistingTranslation? 9 | ) 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/setup/domain/model/SetupProcessModel.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.setup.domain.model 2 | 3 | /** 4 | * A simple domain model to pass data between the steps of the setup process. 5 | */ 6 | data class SetupProcessModel( 7 | var pubspecContent: String, 8 | var l10nFileContent: String? = null, 9 | var mainAppFileContent: String, 10 | var mainAppFilePath: String = "lib/main.dart" // Default path, can be updated 11 | ) 12 | 13 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/client/BatchClientTranslationRequest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.client 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 4 | 5 | /** 6 | * Batch request for creating multiple complex ARB entries at once 7 | */ 8 | data class BatchClientTranslationRequest( 9 | val targetLanguages: List, 10 | val requests: List 11 | ) 12 | 13 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/BasePathSettingMissing.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | class BasePathSettingMissing : UserSettingsException { 6 | constructor() : super() 7 | constructor(message: String?) : super(message) 8 | constructor(message: String?, cause: Throwable?) : super(message, cause) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/GatherTranslationContextService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.TranslateKeyContext 4 | 5 | /** 6 | * gather information required to request the information from the user 7 | */ 8 | fun interface GatherTranslationContextService { 9 | fun gatherTranslationContext(statement: String?): TranslateKeyContext 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/FilePrefixSettingMissing.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | class FilePrefixSettingMissing : UserSettingsException { 6 | constructor() : super() 7 | constructor(message: String?) : super(message) 8 | constructor(message: String?, cause: Throwable?) : super(message, cause) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/GatherContextException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | open class GatherContextException : GPTHelperBaseException { 6 | constructor() : super() 7 | constructor(message: String?) : super(message) 8 | constructor(message: String?, cause: Throwable?) : super(message, cause) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/UserSettingsException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | open class UserSettingsException : GPTHelperBaseException { 6 | constructor() : super() 7 | constructor(message: String?) : super(message) 8 | constructor(message: String?, cause: Throwable?) : super(message, cause) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/exception/BestGuessClientException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.exception 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | open class BestGuessClientException : GPTHelperBaseException { 6 | constructor() : super() 7 | constructor(message: String?) : super(message) 8 | constructor(message: String?, cause: Throwable?) : super(message, cause) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/client/TaskAmountCalculator.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.client 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.TranslateKeyContext 4 | 5 | /** 6 | * Because the translation is broken down into multiple tasks, and we don't know how many tasks, we need to get the information 7 | */ 8 | fun interface TaskAmountCalculator { 9 | fun calculate(context: TranslateKeyContext): Int 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/domain/factories/TranslationRequestFactory.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.factories 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.client.FileTranslationRequest 4 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.entity.TranslateFileContext 5 | 6 | fun interface TranslationRequestFactory { 7 | fun createRequest(translateFileContext: TranslateFileContext): FileTranslationRequest 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/TranslationRequestException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Translation 5 | 6 | class TranslationRequestException(message: String, cause: Throwable, private var targetTranslation: Translation) : GPTHelperBaseException(message, cause) { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/UserSettingsCorruptException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions.UserSettingsException 4 | 5 | class UserSettingsCorruptException : UserSettingsException { 6 | constructor() : super() 7 | constructor(message: String?) : super(message) 8 | constructor(message: String?, cause: Throwable?) : super(message, cause) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/UserSettingsMissingException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions.UserSettingsException 4 | 5 | class UserSettingsMissingException : UserSettingsException { 6 | constructor() : super() 7 | constructor(message: String?) : super(message) 8 | constructor(message: String?, cause: Throwable?) : super(message, cause) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/domain/service/GatherFileTranslationContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.entity.TranslateFileContext 4 | import java.util.UUID 5 | 6 | /** 7 | * gather information required to request the information from the user 8 | */ 9 | fun interface GatherFileTranslationContext { 10 | fun gatherTranslationContext(uuid: UUID): TranslateFileContext? 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/missingtranslations/domain/repository/ExistingTranslationRepository.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.repository 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.entity.ExistingTranslation 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 5 | 6 | interface ExistingTranslationRepository { 7 | fun getExistingTranslation(reference: T, baseLanguage: Language, key: String): ExistingTranslation? 8 | } -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/VerifyTranslationSettingsService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | /** 4 | * ensure that all settings are set, which are required to fulfill the current translation request 5 | */ 6 | fun interface VerifyTranslationSettingsService { 7 | /** 8 | * whether the settings are set, if not, the user should be informed / asked to set them 9 | */ 10 | fun verifySettingsAndInformUserIfInvalid(): Boolean 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/TranslationClientConnectionException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | open class TranslationClientConnectionException : GPTHelperBaseException { 6 | constructor() : super() 7 | constructor(message: String?) : super(message) 8 | constructor(message: String?, cause: Throwable?) : super(message, cause) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/domain/service/PartialFileResponseHandler.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.client.PartialFileTranslationResponse 4 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.entity.TranslateFileContext 5 | 6 | fun interface PartialFileResponseHandler { 7 | fun handlePartialFileResponse(context: TranslateFileContext, partialFileTranslationResponse: PartialFileTranslationResponse) 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/shared/presentation/mapper/UserSettingsDTOMapper.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.shared.presentation.mapper 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.infrastructure.model.UserSettings 4 | import de.keeyzar.gpthelper.gpthelper.features.shared.presentation.dto.UserSettingsDTO 5 | import org.mapstruct.Mapper 6 | 7 | @Mapper 8 | interface UserSettingsDTOMapper { 9 | fun toModel(userSettingsDTO: UserSettingsDTO): UserSettings 10 | fun toDTO(userSettings: UserSettings): UserSettingsDTO 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/FileToTranslate.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | /** 4 | * is the representation of a file in our domain 5 | */ 6 | data class FileToTranslate( 7 | /** 8 | * the language of the file 9 | */ 10 | val language: Language, 11 | /** 12 | * the content of the file, e.g. arb is JSON, but there might be other formats 13 | * might theoretically be a simple translation entry 14 | */ 15 | val content: String, 16 | ) { 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/translationdialogdata/TranslationDialogDataException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions.translationdialogdata 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | open class TranslationDialogDataException : GPTHelperBaseException { 6 | constructor() : super() 7 | constructor(message: String?) : super(message) 8 | constructor(message: String?, cause: Throwable?) : super(message, cause) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/entity/PreFilterRequest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity 2 | 3 | import com.intellij.psi.PsiElement 4 | 5 | /** 6 | * Request for pre-filtering string literals to determine which ones should be translated 7 | */ 8 | data class PreFilterRequest( 9 | val literals: List 10 | ) 11 | 12 | data class PreFilterLiteral( 13 | val id: String, 14 | val literalText: String, 15 | val context: String, 16 | val psiElement: PsiElement 17 | ) 18 | 19 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/review/infrastructure/mapper/ReviewSettingsMapper.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.review.infrastructure.mapper 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.review.domain.entity.ReviewSettings 4 | import de.keeyzar.gpthelper.gpthelper.features.review.infrastructure.model.ReviewSettingsModel 5 | import org.mapstruct.Mapper 6 | 7 | @Mapper 8 | interface ReviewSettingsMapper { 9 | fun toModel(entity: ReviewSettings): ReviewSettingsModel 10 | fun toEntity(model: ReviewSettingsModel): ReviewSettings 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/infrastructure/mapper/FileTranslationRequestMapper.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.infrastructure.mapper 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.client.FileTranslationRequest 5 | 6 | class FileTranslationRequestMapper( 7 | private val objectMapper: ObjectMapper, 8 | ) { 9 | fun mapToContentString(fileTranslationRequest: FileTranslationRequest): List { 10 | TODO() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/presentation/exception/PsiElementException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.presentation.exception 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | /** 6 | * when there was some issue with the statements 7 | */ 8 | class PsiElementException : GPTHelperBaseException { 9 | constructor() : super() 10 | constructor(message: String?) : super(message) 11 | constructor(message: String?, cause: Throwable?) : super(message, cause) 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/translationdialogdata/TranslationDialogDataCorruptException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions.translationdialogdata 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions.UserSettingsException 4 | 5 | class TranslationDialogDataCorruptException : UserSettingsException { 6 | constructor() : super() 7 | constructor(message: String?) : super(message) 8 | constructor(message: String?, cause: Throwable?) : super(message, cause) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/translationdialogdata/TranslationDialogDataMissingException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions.translationdialogdata 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions.UserSettingsException 4 | 5 | class TranslationDialogDataMissingException : UserSettingsException { 6 | constructor() : super() 7 | constructor(message: String?) : super(message) 8 | constructor(message: String?, cause: Throwable?) : super(message, cause) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/psiutils/PsiElementIdGenerator.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.psiutils 2 | 3 | import com.intellij.openapi.application.runReadAction 4 | import com.intellij.psi.PsiElement 5 | 6 | class PsiElementIdGenerator { 7 | /** 8 | * hash the psi element and return a unique id 9 | * TODO: this id generation here is not really secure 10 | */ 11 | fun createIdFromPsiElement(psiElement: PsiElement): String { 12 | return runReadAction { 13 | psiElement.text.hashCode().toString() 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/client/TranslateKeyTaskAmountCalculator.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.client 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.client.TaskAmountCalculator 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.TranslateKeyContext 5 | 6 | class TranslateKeyTaskAmountCalculator : TaskAmountCalculator { 7 | override fun calculate(context: TranslateKeyContext): Int { 8 | return context.availableLanguages.size 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/shared/infrastructure/service/IdeaPathProvider.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.shared.infrastructure.service 2 | 3 | import com.intellij.openapi.project.Project 4 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.service.PathProvider 5 | 6 | /** 7 | * IntelliJ IDEA implementation of the PathProvider. 8 | * For this example, it returns a dummy path. 9 | */ 10 | class IdeaPathProvider(val project: Project) : PathProvider { 11 | override fun getRootPath(): String { 12 | return project.basePath!! 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/NoTranslationFilesException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | /** 6 | * one could theoretically ask the user to create translation files 7 | */ 8 | open class NoTranslationFilesException : GPTHelperBaseException { 9 | constructor() : super() 10 | constructor(message: String?) : super(message) 11 | constructor(message: String?, cause: Throwable?) : super(message, cause) 12 | } 13 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/ExecuteGenCommandProcessServiceException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | /** 6 | * when there was an issue executing the gen command 7 | */ 8 | open class ExecuteGenCommandProcessServiceException : GPTHelperBaseException { 9 | constructor() : super() 10 | constructor(message: String?) : super(message) 11 | constructor(message: String?, cause: Throwable?) : super(message, cause) 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/ContentModificationService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.FileToTranslate 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Translation 5 | 6 | interface ContentModificationService { 7 | fun appendTranslation(fileToTranslate: FileToTranslate, translation: Translation) : FileToTranslate 8 | fun replaceWithNewTranslation(fileToTranslate: FileToTranslate, translation: Translation) : FileToTranslate 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/client/ClientTranslationRequest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.client 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Translation 5 | 6 | data class ClientTranslationRequest ( 7 | /** 8 | * the languages to translate to 9 | */ 10 | val targetLanguages: List, 11 | /** 12 | * this is the base translation to translate from 13 | */ 14 | val translation: Translation, 15 | ) { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/mapper/UserSettingsMapper.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.mapper 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.infrastructure.model.UserSettings 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.repository.UserSettingsPersistentStateComponent.UserSettingsModel 5 | import org.mapstruct.Mapper 6 | 7 | @Mapper 8 | interface UserSettingsMapper { 9 | fun toEntity(userSettingsModel: UserSettingsModel): UserSettings 10 | fun toModel(userSettings: UserSettings): UserSettingsModel 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/psiutils/filter/ImportStatementFilterDartString.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.psiutils.filter 2 | 3 | import com.intellij.psi.PsiElement 4 | import com.intellij.psi.util.parentOfType 5 | import com.jetbrains.lang.dart.psi.DartImportOrExportStatement 6 | 7 | /** 8 | * filters importStatements 9 | */ 10 | class ImportStatementFilterDartString : DartStringLiteralFilter { 11 | override fun filter(psiElement: PsiElement): Boolean { 12 | psiElement.parentOfType()?.let { 13 | return false 14 | } 15 | return true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/repository/UserSettingsRepository.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.infrastructure.model.UserSettings 4 | 5 | interface UserSettingsRepository { 6 | /** 7 | * provides the old userSettings, so you can modify it, and return it with the copy function 8 | */ 9 | fun overrideSettings(callback: (oldUserSettings: UserSettings) -> UserSettings) 10 | fun getSettings(): UserSettings 11 | fun saveSettings(userSettings: UserSettings) 12 | fun wipeUserSettings() 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/repository/UserTranslationInputRepository.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.UserTranslationInput 4 | 5 | interface UserTranslationInputRepository { 6 | fun appendTranslationDialogData(userTranslationInput: UserTranslationInput) 7 | fun saveTranslationDialogData(userTranslationDialogData: List) 8 | fun getLatestTranslationDialogData(): UserTranslationInput? 9 | fun getAllTranslationDialogData(): List 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/client/BestGuessL10nClient.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.client 2 | 3 | /** 4 | * will provide best guesses for translation keys, so the user does not have to type so many manually 5 | */ 6 | interface BestGuessL10nClient { 7 | /** 8 | * provides, based on context, l10n keys and descriptions 9 | * simple in context of the l10n type, i.e. you could have a more complex type having parameters for example 10 | */ 11 | suspend fun simpleGuess(bestGuessRequest: BestGuessRequest, progressReport: (() -> Unit)? = null): BestGuessResponse 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/CurrentFileModificationException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | /** 6 | * when there were issues with manipulation of the current file, where we want to fix the statements / imports 7 | */ 8 | open class CurrentFileModificationException : GPTHelperBaseException { 9 | constructor() : super() 10 | constructor(message: String?) : super(message) 11 | constructor(message: String?, cause: Throwable?) : super(message, cause) 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/missingtranslations/domain/service/MissingTranslationInputService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.entity.MissingTranslationFilteredTargetTranslation 4 | import de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.entity.MissingTranslationAndExistingTranslation 5 | 6 | interface MissingTranslationInputService { 7 | fun collectMissingTranslationInput(missingTranslationAndExistingTranslations: List): List 8 | } -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/shared/domain/exception/GPTHelperBaseException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception 2 | 3 | open class GPTHelperBaseException : Throwable { 4 | constructor(message: String?, cause: Throwable?) : super(message, cause) 5 | constructor(message: String?) : super(message) 6 | constructor(cause: Throwable?) : super(cause) 7 | constructor() : super() 8 | constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( 9 | message, 10 | cause, 11 | enableSuppression, 12 | writableStackTrace 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/SimpleTranslationEntry.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | /** 4 | * single entry in a translation file 5 | */ 6 | data class SimpleTranslationEntry( 7 | //used as some kind of identifier 8 | //TODO remove the nullable id, translation process is the culprit 9 | val id: String?, 10 | val desiredKey: String, 11 | val desiredValue: String, 12 | val desiredDescription: String, 13 | /** 14 | * placeholder might be null, when there is nothing to replace 15 | */ 16 | val placeholder: Map? = null 17 | ) { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/keeyzar/gpthelper/MyBundle.kt: -------------------------------------------------------------------------------- 1 | package com.github.keeyzar.gpthelper 2 | 3 | import com.intellij.DynamicBundle 4 | import org.jetbrains.annotations.NonNls 5 | import org.jetbrains.annotations.PropertyKey 6 | 7 | @NonNls 8 | private const val BUNDLE = "messages.MyBundle" 9 | 10 | object MyBundle : DynamicBundle(BUNDLE) { 11 | 12 | @JvmStatic 13 | fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = 14 | getMessage(key, *params) 15 | 16 | @Suppress("unused") 17 | @JvmStatic 18 | fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = 19 | getLazyMessage(key, *params) 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/review/domain/entity/ReviewSettings.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.review.domain.entity 2 | 3 | data class ReviewSettings( 4 | /** 5 | * so we can ensure to ask only once a week or something like that 6 | */ 7 | val lastReviewRequestTimestamp: Long?, 8 | /** 9 | * how many times the user 10 | */ 11 | val timesReviewRequestSkipped: Long, 12 | /** 13 | * if the user has not yet answered on this one 14 | */ 15 | val shouldAskLater: Boolean?, 16 | /** 17 | * if the user has already reviewed, or does not wish to review 18 | */ 19 | val reviewed: Boolean, 20 | ) 21 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/parser/GPTARBResponseParser.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.parser 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.dto.GPTArbTranslationResponse 5 | 6 | class GPTARBResponseParser(private val jacksonMapper: ObjectMapper) { 7 | 8 | fun parseResponse(response: String): GPTArbTranslationResponse { 9 | val readValue = jacksonMapper.readValue(response, Map::class.java) as Map 10 | return GPTArbTranslationResponse(content = readValue) 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/flutter_intl/domain/repository/FlutterIntlSettingsRepository.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.flutter_intl.domain.repository 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.flutter_intl.domain.entity.FlutterIntlSettings 4 | import java.nio.file.Path 5 | 6 | /** 7 | * provides flutter intl settings, if there are any 8 | * caution, the user might have settings, which he uses in the console, therefore this might not be the ground truth 9 | */ 10 | interface FlutterIntlSettingsRepository { 11 | fun getFlutterIntlSettings(): FlutterIntlSettings 12 | fun loadFlutterIntlSettingsByPath(path: Path): FlutterIntlSettings 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/service/ARBTemplateService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service 2 | 3 | class ARBTemplateService { 4 | fun fillTemplate(key: String, value: String, description: String): String { 5 | return template.replace("", key) 6 | .replace("", value) 7 | .replace("", description) 8 | } 9 | 10 | private val template = """ 11 | { 12 | "": "", 13 | "@": { 14 | "description": "" 15 | } 16 | } 17 | """.trimIndent() 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/CurrentFileModificationService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Translation 4 | 5 | /** 6 | * differs from [ArbFileModificationService] because this should modify the current file, e.g. often you need to add e.g. an import, change some variable names... whatever 7 | */ 8 | fun interface CurrentFileModificationService { 9 | /** 10 | * this is something, which should be executed immediately, e.g. when the user presses a button 11 | */ 12 | fun modifyCurrentFile(translation: Translation) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/service/TranslationProgressChangeNotifier.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service 2 | 3 | import com.intellij.util.messages.Topic 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.TranslationProgress 5 | 6 | 7 | interface TranslationProgressChangeNotifier { 8 | fun afterAction(translationProgress: TranslationProgress) 9 | 10 | companion object { 11 | val CHANGE_ACTION_TOPIC = Topic.create( 12 | "web browser", 13 | TranslationProgressChangeNotifier::class.java, Topic.BroadcastDirection.TO_PARENT 14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/intentionDescriptions/GenerateArbIntentionAction/description.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | translate this string and put it into the ARB files 4 |

5 | 1. Will replace the string with the accessor to the translations (currently S, can be configured) 6 | 2. Will add imports, if necessary 7 | 3. Will put a dummy translation first, so that the UI is not blocked 8 | 4. When the translation, in the background, finished, it will replace the dummy translation with the real one 9 | 5. Will call flutter gen-l10n when the dummy has been put into place 10 |

11 | 12 |

There is no more information. Hello world, this is my first real plugin. Sorry for that.

13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/TranslationFileNotFound.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 5 | import java.nio.file.Path 6 | 7 | class TranslationFileNotFound(message: String, private var language: Language, private var targetPath: Path) : GPTHelperBaseException(message) { 8 | 9 | override val message: String = "" 10 | get() = "Target file not found for language '${language.toISOLangString()}'. File path searched: '$targetPath' + $field" 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/missingtranslations/domain/service/MissingTranslationCollectionService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.entity.MissingTranslationContext 4 | 5 | interface MissingTranslationCollectionService { 6 | 7 | /** 8 | * Search for missing translations based on the given context and their reference. 9 | * In intelliJ the reference is a reference to the File, but e.g. in visual studio code it would be something else 10 | */ 11 | fun collectMissingTranslations(missingTranslationContext: MissingTranslationContext): MissingTranslationContext 12 | } -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/infrastructure/service/OpenAIMultiKeyTranslationTaskSizeEstimator.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.infrastructure.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity.MultiKeyTranslationContext 4 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.service.MultiKeyTranslationTaskSizeEstimator 5 | 6 | class OpenAIMultiKeyTranslationTaskSizeEstimator : MultiKeyTranslationTaskSizeEstimator { 7 | override fun estimateTaskSize(multiKeyTranslationContext: MultiKeyTranslationContext): Int { 8 | return multiKeyTranslationContext.translationEntries.size * multiKeyTranslationContext.targetLanguages.size 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/client/PartialTranslationResponse.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.client 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Translation 5 | 6 | /** 7 | * represents all translations for all files, because the original translation might be modified because 8 | * of wording etc. 9 | */ 10 | class PartialTranslationResponse(var translation: Translation) { 11 | 12 | fun getTranslationKey(): String { 13 | return translation.entry.desiredKey 14 | } 15 | 16 | fun getTargetLanguage(): Language { 17 | return translation.lang 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/domain/entity/TranslateFileContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.entity 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 4 | import java.nio.file.Path 5 | 6 | /** 7 | * contains information about the current request to translate a key, 8 | * though from the perspective of us, asking the user for information 9 | * 10 | */ 11 | data class TranslateFileContext( 12 | /** 13 | * the current statement to be translated 14 | * most of the time a StringLiteral, but might be something different in the future 15 | */ 16 | val baseLanguage: Language, 17 | val targetLanguage: Language, 18 | val absolutePath: Path, 19 | ) { 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/Language.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | //en_US or en 4 | data class Language(val lang: String, val country: String?) { 5 | companion object { 6 | fun fromISOLangString(isoLang: String): Language { 7 | val split = isoLang.split("_") 8 | return if (split.size == 2) { 9 | Language(split[0], split[1]) 10 | } else { 11 | Language(split[0], null) 12 | } 13 | } 14 | } 15 | 16 | 17 | fun toISOLangString(): String { 18 | return if (country != null) { 19 | "${lang}_${country}" 20 | } else { 21 | lang 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/client/SingleTranslationRequestClient.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.client 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.dto.GPTArbTranslationResponse 5 | 6 | interface SingleTranslationRequestClient { 7 | suspend fun requestTranslation(key: String, value: String, description: String, targetLanguage: String): GPTArbTranslationResponse 8 | suspend fun requestTranslation(content: String, targetLanguage: String): GPTArbTranslationResponse 9 | suspend fun requestTranslation(content: String, baseLanguage: Language, targetLanguage: Language): GPTArbTranslationResponse 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/service/GatherBestGuessContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.service 2 | 3 | import com.intellij.psi.PsiElement 4 | import com.intellij.psi.PsiFile 5 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity.FileBestGuessContext 6 | import java.util.* 7 | 8 | interface GatherBestGuessContext { 9 | /** 10 | * Gathers the context from multiple files, usually without user interaction for string selection. 11 | * @return null, if no relevant literals are found. 12 | */ 13 | fun fromMultipleFiles(processUUID: UUID, files: List): FileBestGuessContext? 14 | 15 | fun fromPsiElements(processUUID: UUID, elements: List): FileBestGuessContext? 16 | } 17 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | # libraries 3 | junit = "4.13.2" 4 | opentest4j = "1.3.0" 5 | 6 | # plugins 7 | changelog = "2.4.0" 8 | intelliJPlatform = "2.7.1" 9 | kotlin = "2.2.0" 10 | kover = "0.9.1" 11 | qodana = "2025.1.1" 12 | 13 | [libraries] 14 | junit = { group = "junit", name = "junit", version.ref = "junit" } 15 | opentest4j = { group = "org.opentest4j", name = "opentest4j", version.ref = "opentest4j" } 16 | 17 | [plugins] 18 | changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" } 19 | intelliJPlatform = { id = "org.jetbrains.intellij.platform", version.ref = "intelliJPlatform" } 20 | kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 21 | kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } 22 | qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/exceptions/TranslationFileModificationException.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.GPTHelperBaseException 4 | 5 | open class TranslationFileModificationException : GPTHelperBaseException { 6 | constructor(message: String?, cause: Throwable?) : super(message, cause) 7 | constructor(message: String?) : super(message) 8 | constructor(cause: Throwable?) : super(cause) 9 | constructor() : super() 10 | constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( 11 | message, 12 | cause, 13 | enableSuppression, 14 | writableStackTrace 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/UserTranslationInput.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | /** 4 | * contains information about the translation 5 | */ 6 | data class UserTranslationInput( 7 | /** 8 | * the desired key of the translation 9 | */ 10 | val desiredKey: String, 11 | /** 12 | * the desired value of the translation 13 | */ 14 | val desiredValue: String, 15 | /** 16 | * the desired description 17 | */ 18 | val desiredDescription: String, 19 | /** 20 | * normally, the user wants to translate all languages 21 | */ 22 | val languagesToTranslate: Map, 23 | /** 24 | * Checkbox value for 'Translate Now' 25 | */ 26 | val translateNow: Boolean 27 | ) { 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/GatherUserInputService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.TranslateKeyContext 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.UserTranslationInput 5 | 6 | /** 7 | * get information from the user, like the desired key, description, ... 8 | */ 9 | fun interface GatherUserInputService { 10 | /** 11 | * e.g. get the data from the user, or calculate it based on the class, whatever 12 | * @param translateKeyContext is information, which helps the process of getting the information from the user 13 | */ 14 | fun requestInformationFromUser(translateKeyContext: TranslateKeyContext): UserTranslationInput? 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/repository/TranslationFileRepository.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.FileToTranslate 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 5 | import java.nio.file.Path 6 | 7 | /** 8 | * this repository is for the domain representation of a translation file. 9 | */ 10 | interface TranslationFileRepository { 11 | fun getTranslationFileByLanguage(language: Language): FileToTranslate 12 | fun createOrGetTranslationFileByLanguage(language: Language): FileToTranslate 13 | fun saveTranslationFile(fileToTranslate: FileToTranslate) 14 | fun getPathsToTranslationFiles(): List 15 | fun getPathToFile(language: Language): Path 16 | } 17 | -------------------------------------------------------------------------------- /src/test/kotlin/features/translations/domain/mapper/ArbFilenameParserTest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.mapper 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.parser.ArbFilenameParser 4 | import org.junit.Test 5 | import org.junit.jupiter.api.Assertions.* 6 | 7 | class ArbFilenameParserTest { 8 | private val sut = ArbFilenameParser() 9 | 10 | @Test 11 | fun `getArbFilePrefix returns correct prefix`() { 12 | assertEquals("ABC", sut.getArbFilePrefix("ABCen_US.arb")) 13 | assertEquals("AB_", sut.getArbFilePrefix("AB_en.arb")) 14 | assertEquals("qw_", sut.getArbFilePrefix("qw_de.arb")) 15 | assertEquals("q123_", sut.getArbFilePrefix("q123_de_DE.arb")) 16 | assertEquals("", sut.getArbFilePrefix("test")) 17 | assertEquals("", sut.getArbFilePrefix("")) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/service/PreFilterRequestBuilder.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.service 2 | 3 | import com.intellij.psi.PsiElement 4 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity.PreFilterRequest 5 | import de.keeyzar.gpthelper.gpthelper.features.psiutils.SmartPsiElementWrapper 6 | 7 | /** 8 | * Service to prepare pre-filter requests from PSI elements 9 | */ 10 | interface PreFilterRequestBuilder { 11 | /** 12 | * Builds a pre-filter request from a map of PSI elements wrapped in SmartPointers 13 | * @param elements Map of SmartPsiElementWrapper to their pre-selection state 14 | * @return PreFilterRequest ready to be sent to the AI 15 | */ 16 | fun buildRequest(elements: Map, Boolean>): PreFilterRequest 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/shared/presentation/actions/ProjectAwareAction.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.shared.presentation.actions 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent 4 | import com.intellij.openapi.project.DumbAwareAction 5 | import com.intellij.openapi.project.Project 6 | import de.keeyzar.gpthelper.gpthelper.features.translations.presentation.dependencyinjection.FlutterArbTranslationInitializer 7 | 8 | abstract class ProjectAwareAction : DumbAwareAction() { 9 | override fun actionPerformed(e: AnActionEvent) { 10 | val project = e.project ?: return 11 | val initializer = FlutterArbTranslationInitializer.create(project) 12 | actionPerformed(e, project, initializer) 13 | } 14 | 15 | abstract fun actionPerformed(e: AnActionEvent, project: Project, initializer: FlutterArbTranslationInitializer) 16 | } 17 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/review/infrastructure/model/ReviewSettingsModel.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.review.infrastructure.model 2 | 3 | class ReviewSettingsModel( 4 | /** 5 | * so we can ensure to ask only once a week or something like that 6 | */ 7 | val lastReviewRequestTimestamp: Long?, 8 | /** 9 | * how many times the user 10 | */ 11 | val timesReviewRequestSkipped: Long, 12 | /** 13 | * if the user has not yet answered on this one 14 | */ 15 | val shouldAskLater: Boolean?, 16 | val reviewed: Boolean, 17 | ) { 18 | companion object { 19 | val DEFAULT: ReviewSettingsModel = ReviewSettingsModel( 20 | lastReviewRequestTimestamp = null, 21 | timesReviewRequestSkipped = 0, 22 | shouldAskLater = null, 23 | reviewed = false, 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/review/domain/config/ReviewConfig.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.review.domain.config 2 | 3 | import java.net.URI 4 | 5 | data class ReviewConfig( 6 | val timeBetweenReviewRequestsInSeconds: Int = TIME_BETWEEN_REVIEW_REQUESTS_IN_SECONDS, 7 | val minimumTranslationsBeforeWeAskForReview: Int = MINIMUM_TRANSLATIONS_BEFORE_WE_ASK_FOR_REVIEW, 8 | val uriFromPlugin: URI = URI_FROM_PLUGIN, 9 | ) { 10 | companion object { 11 | //is there any cool utilities function for this? 12 | private const val TWO_DAYS_IN_SECONDS = 2 * 24 * 60 * 60 13 | private const val TIME_BETWEEN_REVIEW_REQUESTS_IN_SECONDS = TWO_DAYS_IN_SECONDS 14 | private const val MINIMUM_TRANSLATIONS_BEFORE_WE_ASK_FOR_REVIEW = 10 15 | private val URI_FROM_PLUGIN = URI("https://plugins.jetbrains.com/plugin/21732-gpt-flutter-intl/reviews") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/mapper/TranslationRequestMapper.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.mapper 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.client.ClientTranslationRequest 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.UserTranslationRequest 5 | import org.mapstruct.Mapper 6 | import org.mapstruct.Mapping 7 | 8 | /** 9 | * map from UserTranslationRequest to ClientTranslationRequest 10 | */ 11 | @Mapper 12 | interface TranslationRequestMapper { 13 | @Mapping(source = "baseTranslation", target = "translation") 14 | fun toClientRequest(userTranslationRequest: UserTranslationRequest): ClientTranslationRequest 15 | @Mapping(source = "translation", target = "baseTranslation") 16 | fun toUserRequest(clientTranslationRequest: ClientTranslationRequest): UserTranslationRequest 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/presentation/service/ArbFormatTranslationFileContentService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.presentation.service 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.FileToTranslate 5 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.service.FormatTranslationFileContentService 6 | 7 | class ArbFormatTranslationFileContentService( 8 | private val objectMapper: ObjectMapper 9 | ) : FormatTranslationFileContentService { 10 | override fun formatTranslationFileContent(fileToTranslate: FileToTranslate): FileToTranslate { 11 | val newContent = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectMapper.readTree(fileToTranslate.content)) 12 | return fileToTranslate.copy(content = newContent) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/shared/presentation/dto/UserSettingsDTO.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.shared.presentation.dto 2 | 3 | data class UserSettingsDTO( 4 | /** 5 | * where is the directory containing the arb files 6 | */ 7 | var arbDir: String = "", 8 | /** 9 | * how is the key names (e.g. S, AppLocalizations) 10 | */ 11 | var outputClass: String = "", 12 | /** 13 | * whether we're working with a nullable getter 14 | */ 15 | var nullableGetter: Boolean = true, 16 | /** 17 | * base file for the translations, i.e. here you can find the prefix 18 | */ 19 | var templateArbFile: String = "", 20 | var intlConfigFile: String = "", 21 | var watchIntlConfigFile: Boolean = true, 22 | /** 23 | * here you can find the file, where the outputClass is located in 24 | */ 25 | var outputLocalizationFile: String = "" 26 | ) 27 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/client/GptModelProviderImpl.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.client 2 | 3 | import com.google.genai.types.ListModelsConfig 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.client.GPTModelProvider 5 | import de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.configuration.LLMConfigProvider 6 | import kotlinx.coroutines.runBlocking 7 | 8 | class GptModelProviderImpl ( 9 | private val LLMConfigProvider: LLMConfigProvider, 10 | ) : GPTModelProvider() { 11 | override fun getAllModels(): List { 12 | return runBlocking { 13 | LLMConfigProvider.getInstanceGemini().models.list(ListModelsConfig.builder().pageSize(20).build()).page() 14 | .map { e -> e.name().orElse("no name") } 15 | .toList(); 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/presentation/actions/AutoLocalizeDirectory.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.presentation.actions 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent 4 | import com.intellij.openapi.actionSystem.CommonDataKeys 5 | import com.intellij.openapi.project.Project 6 | import de.keeyzar.gpthelper.gpthelper.features.shared.presentation.actions.ProjectAwareAction 7 | import de.keeyzar.gpthelper.gpthelper.features.translations.presentation.dependencyinjection.FlutterArbTranslationInitializer 8 | 9 | class AutoLocalizeDirectory : ProjectAwareAction() { 10 | override fun actionPerformed(e: AnActionEvent, project: Project, initializer: FlutterArbTranslationInitializer) { 11 | val directory = e.getData(CommonDataKeys.VIRTUAL_FILE) ?: return 12 | initializer.orchestrator.orchestrate(project, directory, "Auto Localizing Directory") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/configuration/LLMConfigProvider.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.configuration 2 | 3 | import com.google.genai.Client 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository.TranslationCredentialsServiceRepository 5 | 6 | class LLMConfigProvider(private val credentialsServiceRepository: TranslationCredentialsServiceRepository) { 7 | 8 | fun getInstanceGemini(): Client { 9 | return credentialsServiceRepository.getKey()?.let { 10 | withKeyGemini(it) 11 | } ?: withoutGeminiKey() 12 | } 13 | 14 | private fun withoutGeminiKey(): Client { 15 | return Client.builder() 16 | .build() 17 | } 18 | 19 | fun withKeyGemini(key: String): Client { 20 | return Client.builder() 21 | .apiKey(key) 22 | .build() 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/infrastructure/search/ArbFileContentProvider.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.infrastructure.search 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.flutter_intl.infrastructure.service.ArbFilesService 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 5 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository.TranslationFileRepository 6 | 7 | class ArbFileContentProvider( 8 | private val arbFilesService: ArbFilesService, 9 | private val translationFileRepository: TranslationFileRepository, 10 | ) { 11 | fun getBaseLangContent(): String { 12 | val lang = com.intellij.openapi.application.ReadAction.compute { 13 | arbFilesService.getBaseLanguage(null) 14 | } 15 | return translationFileRepository.getTranslationFileByLanguage(lang).content 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/service/GuessAdaptionService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.client.BestGuessResponse 4 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity.MultiKeyTranslationContext 5 | import java.util.* 6 | 7 | /** 8 | * we have a lot of guesses now, that must be checked and modified (by the user most definitely) 9 | */ 10 | fun interface GuessAdaptionService { 11 | /** 12 | * the user now has a lot of possible translation entries, we need to allow him to modify them 13 | * afterward we take all these l10n translation requests and exchange them in the file 14 | * 15 | * @return null, when the user has interrupted the process 16 | */ 17 | fun adaptBestGuess(processUUID: UUID, bestGuessResponse: BestGuessResponse): MultiKeyTranslationContext? 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/service/ImmediateTranslationService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.dto.GPTArbTranslationResponse 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.parser.GPTARBResponseParser 5 | 6 | 7 | /** 8 | * will immediately provide a translation 9 | */ 10 | class ImmediateTranslationService( 11 | private val templateService: ARBTemplateService, 12 | private val responseParser: GPTARBResponseParser, 13 | ) { 14 | /** 15 | * gpt skips the own translation, therefore we provide multiple "responses" 16 | */ 17 | fun requestTranslation(key: String, value: String, description: String): GPTArbTranslationResponse { 18 | return responseParser.parseResponse(templateService.fillTemplate(key, value, description)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/presentation/actions/DartStringRequiredPattern.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.presentation.actions 2 | 3 | import com.intellij.patterns.PatternCondition 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.psi.util.PsiTreeUtil 6 | import com.intellij.psi.util.elementType 7 | import com.intellij.util.ProcessingContext 8 | import com.jetbrains.lang.dart.DartTokenTypes 9 | 10 | 11 | class DartStringRequiredPattern : PatternCondition("DartStringRequiredPattern") { 12 | override fun accepts( 13 | t: PsiElement, 14 | context: ProcessingContext? 15 | ): Boolean { 16 | val types = listOf( 17 | DartTokenTypes.ARGUMENT_LIST, 18 | DartTokenTypes.VAR_DECLARATION_LIST 19 | ) 20 | 21 | return PsiTreeUtil.findFirstParent(t) { 22 | types.contains(it.elementType) 23 | } != null 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/TranslateKeyContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | /** 4 | * contains information about the current request to translate a key, 5 | * though from the perspective of us, asking the user for information 6 | * 7 | */ 8 | data class TranslateKeyContext( 9 | /** 10 | * the current statement to be translated 11 | * most of the time a StringLiteral, but might be something different in the future 12 | */ 13 | val statement: String, 14 | /** 15 | * which is the language this translation stems from? 16 | */ 17 | val baseLanguage: Language, 18 | /** 19 | * we might want to show user information based on the last input 20 | */ 21 | val lastUserInput: List?, 22 | val availableLanguages: List, 23 | var changeTranslationContext: ChangeTranslationContext? = null 24 | ) { 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/flutter_intl/domain/entity/FlutterIntlSettings.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.flutter_intl.domain.entity 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties 4 | import com.fasterxml.jackson.annotation.JsonProperty 5 | 6 | @JsonIgnoreProperties(ignoreUnknown = true) 7 | data class FlutterIntlSettings( 8 | @JsonProperty("arb-dir") 9 | var arbDir: String = "lib/l10n", 10 | @JsonProperty("template-arb-file") 11 | var templateArbFile: String = "app_en.arb", 12 | @JsonProperty("output-localization-file") 13 | var outputLocalizationFile: String = "lib/l10n/app_localizations.dart", 14 | @JsonProperty("output-class") 15 | var outputClass: String = "AppLocalizations", 16 | @JsonProperty("nullable-getter") 17 | var nullableGetter: Boolean = true, 18 | @JsonProperty("untranslated-messages-file") 19 | var untranslatedMessagesFile: String = "untranslated_messages.txt", 20 | ) 21 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/missingtranslations/domain/entity/MissingTranslationFilteredTargetTranslation.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.entity 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 4 | 5 | /** 6 | * the missing translation e.g. "greeting" for de and pl - the user said he wants to translate it to de only 7 | */ 8 | data class MissingTranslationFilteredTargetTranslation( 9 | val missingTranslationAndExistingTranslation: MissingTranslationAndExistingTranslation, 10 | val languagesToTranslateTo: List, 11 | val uuid: String, 12 | ) { 13 | override fun equals(other: Any?): Boolean { 14 | if (this === other) return true 15 | if (other !is MissingTranslationFilteredTargetTranslation) return false 16 | return uuid == other.uuid 17 | } 18 | 19 | override fun hashCode(): Int { 20 | return uuid.hashCode() 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/client/PreFilterClient.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.client 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity.PreFilterRequest 4 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity.PreFilterResponse 5 | 6 | /** 7 | * Client for AI-based pre-filtering of string literals to determine which should be translated 8 | */ 9 | interface PreFilterClient { 10 | /** 11 | * Analyzes string literals and determines which ones should be translated 12 | * @param request The pre-filter request containing literals with context 13 | * @param progressCallback Optional callback invoked after each batch is processed 14 | * @return Response indicating which literals should be translated 15 | */ 16 | suspend fun preFilter(request: PreFilterRequest, progressCallback: ((Int, Int) -> Unit)? = null): PreFilterResponse 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/TranslationTriggeredHooks.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Translation 4 | 5 | /** 6 | * different kind of translation process hooks 7 | */ 8 | interface TranslationTriggeredHooks { 9 | /** 10 | * when you have a single key translation and want it to be triggered 11 | * combines [translationTriggeredInit] + [translationTriggeredPartial] 12 | */ 13 | fun translationTriggered(translation: Translation) 14 | 15 | /** 16 | * when you have multiple keys, which must be replaced 17 | */ 18 | fun translationTriggeredPartial(translation: Translation) 19 | 20 | /** 21 | * If there is some kind of initial task to be done once, which is kinda expensive 22 | */ 23 | fun translationTriggeredInit() 24 | fun translationTriggeredPostTranslation() 25 | } 26 | -------------------------------------------------------------------------------- /src/test/kotlin/features/psiutils/filter/ImportStatementFilterDartStringTest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.psiutils.filter 2 | 3 | import com.intellij.psi.util.descendantsOfType 4 | import com.intellij.testFramework.fixtures.BasePlatformTestCase 5 | import com.jetbrains.lang.dart.psi.DartStringLiteralExpression 6 | 7 | class ImportStatementFilterDartStringTest : BasePlatformTestCase() { 8 | 9 | private lateinit var sut: ImportStatementFilterDartString 10 | 11 | override fun setUp() { 12 | super.setUp() 13 | sut = ImportStatementFilterDartString() 14 | myFixture.testDataPath = "src/test/resources/psiutils/dart/filter" 15 | } 16 | 17 | fun testShouldFilterImportStatements() { 18 | val psiFile = myFixture.configureByFile("import_string_literal.dart") 19 | val stringLiteral = psiFile.descendantsOfType().first() 20 | 21 | val filter = sut.filter(stringLiteral) 22 | 23 | assertFalse(filter) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/KoinInstances.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper 2 | 3 | import com.intellij.openapi.project.Project 4 | import org.koin.core.Koin 5 | import org.koin.core.KoinApplication 6 | import org.koin.core.module.Module 7 | import org.koin.dsl.koinApplication 8 | 9 | object KoinInstances { 10 | private val projectKoinApps = mutableMapOf() 11 | 12 | fun start(project: Project, modules: List) { 13 | println("Starting Koin for project ${project.name}") 14 | val koinApp = koinApplication { 15 | modules(modules) 16 | } 17 | projectKoinApps[project] = koinApp 18 | } 19 | 20 | fun get(project: Project): Koin { 21 | return projectKoinApps[project]?.koin ?: error("Koin not started for project ${project.name}") 22 | } 23 | 24 | fun close(project: Project) { 25 | projectKoinApps.remove(project)?.close() 26 | println("Closed Koin for project ${project.name}") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/IdeaTranslationErrorProcessHandlerImpl.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.ui.DialogBuilder 5 | import com.intellij.ui.dsl.builder.panel 6 | 7 | class IdeaTranslationErrorProcessHandlerImpl : TranslationErrorProcessHandler { 8 | override fun displayErrorToUser(e: Throwable) { 9 | e.printStackTrace() 10 | ApplicationManager.getApplication().invokeAndWait { 11 | DialogBuilder().apply { 12 | setTitle("Error") 13 | setCenterPanel( 14 | panel { 15 | row { 16 | label(e.message ?: "Unknown Error, please check logs") 17 | } 18 | } 19 | ) 20 | addOkAction() 21 | }.showAndGet() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/service/IdeaTranslationProgressBus.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.util.messages.MessageBus 5 | import com.intellij.util.messages.MessageBusConnection 6 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.TranslationProgress 7 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.service.TranslationProgressBus 8 | 9 | class IdeaTranslationProgressBus( 10 | val project: Project 11 | ) : TranslationProgressBus { 12 | 13 | override fun pushPercentage(translationProgress: TranslationProgress) { 14 | val messageBus: MessageBus = project.messageBus 15 | val connection: MessageBusConnection = messageBus.connect() 16 | messageBus.syncPublisher(TranslationProgressChangeNotifier.CHANGE_ACTION_TOPIC) 17 | .afterAction(translationProgress) 18 | connection.disconnect() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/project/ProjectKoinService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.project 2 | 3 | import com.intellij.openapi.Disposable 4 | import com.intellij.openapi.project.Project 5 | import de.keeyzar.gpthelper.gpthelper.KoinInstances 6 | import de.keeyzar.gpthelper.gpthelper.createAppModule 7 | import org.koin.core.Koin 8 | 9 | class ProjectKoinService(private val project: Project) : Disposable { 10 | 11 | fun start() { 12 | //Each project will have its own Koin context, isolated from others. 13 | KoinInstances.start(project, listOf(createAppModule(project))) 14 | } 15 | 16 | fun getKoin(): Koin { 17 | return KoinInstances.get(project) 18 | } 19 | 20 | override fun dispose() { 21 | // We must stop Koin to prevent memory leaks when the project is closed. 22 | KoinInstances.close(project) 23 | } 24 | 25 | companion object { 26 | fun getInstance(project: Project): ProjectKoinService { 27 | return project.getService(ProjectKoinService::class.java) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/de/keeyzar/gpthelper/gpthelper/features/flutterarb/presentation/handler/ArbFileType.java: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.flutterarb.presentation.handler; 2 | 3 | import com.intellij.icons.AllIcons; 4 | import com.intellij.json.JsonLanguage; 5 | import com.intellij.openapi.fileTypes.LanguageFileType; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import javax.swing.*; 9 | 10 | public class ArbFileType extends LanguageFileType { 11 | public static final ArbFileType INSTANCE = new ArbFileType(); 12 | 13 | private ArbFileType() { 14 | super(JsonLanguage.INSTANCE); 15 | } 16 | 17 | @NotNull 18 | @Override 19 | public String getName() { 20 | return "ARB File"; 21 | } 22 | 23 | @NotNull 24 | @Override 25 | public String getDescription() { 26 | return "Application resource bundle (.arb) file"; 27 | } 28 | 29 | @NotNull 30 | @Override 31 | public String getDefaultExtension() { 32 | return "arb"; 33 | } 34 | 35 | @Override 36 | public Icon getIcon() { 37 | return AllIcons.FileTypes.Json; 38 | } 39 | } -------------------------------------------------------------------------------- /.run/Run Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | true 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/test/kotlin/features/psiutils/LiteralInContextFinderTest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.psiutils 2 | 3 | import com.intellij.psi.util.descendantsOfType 4 | import com.intellij.testFramework.fixtures.BasePlatformTestCase 5 | import com.jetbrains.lang.dart.psi.DartStringLiteralExpression 6 | 7 | class LiteralInContextFinderTest : BasePlatformTestCase() { 8 | private lateinit var sut: LiteralInContextFinder 9 | 10 | override fun setUp() { 11 | super.setUp() 12 | sut = LiteralInContextFinder() 13 | myFixture.testDataPath = "src/test/resources" 14 | } 15 | 16 | fun testFindLiteralInContext() { 17 | val psiFile = myFixture.configureByText("Test.dart", """ 18 | void main() { 19 | String complexString = "hello" + 3 + "world"; 20 | } 21 | """.trimIndent()) 22 | 23 | val stringLiteral = psiFile.descendantsOfType().first() 24 | 25 | val literalInContext = sut.findContext(stringLiteral) 26 | 27 | assertEquals("""String complexString = "hello" + 3 + "world"""", literalInContext.text) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.run/Run Plugin.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.run/Run Verifications.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/infrastructure/parser/BestGuessOpenAIResponseParser.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.infrastructure.parser 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import com.fasterxml.jackson.module.kotlin.readValue 5 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.client.BestGuessResponse 6 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.client.BestGuessResponseEntry 7 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.exception.BestGuessClientException 8 | 9 | class BestGuessOpenAIResponseParser( 10 | private val objectMapper: ObjectMapper, 11 | ) { 12 | fun parse(response: String): BestGuessResponse { 13 | return try { 14 | val entries = objectMapper.readValue>(response) 15 | BestGuessResponse(responseEntries = entries) 16 | } catch (e: Throwable){ 17 | e.printStackTrace() 18 | throw BestGuessClientException("Could not parse the response of chatGPT\nResponse:\n$response") 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/kotlin/features/shared/infrastructure/model/UserSettingsTest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.shared.infrastructure.model 2 | 3 | import org.junit.Test 4 | import org.junit.jupiter.api.Assertions.* 5 | 6 | class UserSettingsTest { 7 | 8 | val sut = UserSettings( 9 | "arbDir", 10 | "outputClass", 11 | false, 12 | "templateArbFile", 13 | "intlConfigFile", 14 | true, 15 | "", 16 | "outputLocalizationFile", 17 | 3, 18 | "", 19 | "gpt-3.5-turbo-0613", 20 | true,10) 21 | 22 | @Test 23 | fun `getArbFilePrefix returns correct prefix`() { 24 | assertEquals("ABC", sut.copy(templateArbFile = "ABCen_US.arb").getArbFilePrefix()) 25 | assertEquals("AB_", sut.copy(templateArbFile = "AB_en.arb").getArbFilePrefix()) 26 | assertEquals("qw_", sut.copy(templateArbFile = "qw_de.arb").getArbFilePrefix()) 27 | assertEquals("q123_", sut.copy(templateArbFile = "q123_de_DE.arb").getArbFilePrefix()) 28 | assertEquals("", sut.copy(templateArbFile = "test").getArbFilePrefix()) 29 | assertEquals("", sut.copy(templateArbFile = "").getArbFilePrefix()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/infrastructure/service/PsiElementIdReferenceProvider.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.infrastructure.service 2 | 3 | import com.intellij.psi.PsiElement 4 | 5 | class PsiElementIdReferenceProvider 6 | { 7 | //hashmap with string id to psiElement 8 | private var elements: MutableMap = mutableMapOf() 9 | 10 | /** 11 | * remember to delete the element after you are done with it, otherwise we have a memory leak.. :) 12 | */ 13 | fun putElement(id: String, element: PsiElement) { 14 | elements[id] = element 15 | } 16 | fun deleteElement(id: String, element: PsiElement) { 17 | elements.remove(id) 18 | } 19 | 20 | fun getElement(id: String): PsiElement? { 21 | return elements[id] 22 | } 23 | 24 | /** 25 | * this should not be necessary, might have multiple translations in the background, and then 26 | * we loose the whole context, though I could theoretically map all the elements to a project (i.e. aggregated Key) 27 | * TODO need to fix this 28 | */ 29 | fun deleteAllElements() { 30 | elements.clear() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/review/domain/service/IdeaAskUserForReviewService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.review.domain.service 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import de.keeyzar.gpthelper.gpthelper.features.review.domain.entity.AskUserForReviewResult 5 | import de.keeyzar.gpthelper.gpthelper.features.review.presentation.widgets.ReviewDialog 6 | import java.util.concurrent.atomic.AtomicReference 7 | 8 | class IdeaAskUserForReviewService : AskUserForReviewService { 9 | override fun askUserForReview(): AskUserForReviewResult? { 10 | val result: AtomicReference = AtomicReference(null); 11 | ApplicationManager.getApplication().invokeAndWait { 12 | val dialog = ReviewDialog() 13 | val closedWithOk = dialog.showAndGet() 14 | if (closedWithOk) { 15 | result.set(dialog.getResult()) 16 | } else { 17 | //we assume he said "ask me later" 18 | result.set(AskUserForReviewResult(closedWithShouldAskLater = true, closedWithReview = false, closedWithDontAskAgain = false)) 19 | } 20 | } 21 | return result.get() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/parser/ARBFileContentParser.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.parser 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import de.keeyzar.gpthelper.gpthelper.features.shared.infrastructure.utils.JsonUtils 5 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.ARBFileContent 6 | import de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.dto.GPTArbTranslationResponse 7 | 8 | class ARBFileContentParser(private val jacksonMapper: ObjectMapper, private val jsonUtils: JsonUtils) { 9 | fun toARB(lang: String, response: GPTArbTranslationResponse): ARBFileContent { 10 | var content = jacksonMapper.writeValueAsString(response.content) 11 | //{..}, 12 | content = jsonUtils.removeTrailingComma(content) 13 | //{"":""}, (maybe), but take care of "": {"":""} 14 | if(content.trim().startsWith("{")) { 15 | content = jsonUtils.removeSurroundingBrackets(content) 16 | } 17 | //"":"", 18 | content = jsonUtils.removeTrailingComma(content) 19 | //"":"" 20 | return ARBFileContent(lang, content) 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/service/FlutterGenCommandProcessService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions.ExecuteGenCommandProcessServiceException 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.service.ConsoleService 5 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.service.ExternalTranslationProcessService 6 | 7 | /** 8 | * implements the domain service for the external translation process 9 | */ 10 | class FlutterGenCommandProcessService( 11 | private val ideaTerminalConsoleService: ConsoleService, 12 | ) : ExternalTranslationProcessService { 13 | companion object { 14 | const val FLUTTER_GEN_COMMAND = "flutter gen-l10n" 15 | } 16 | override fun postTranslationProcess() { 17 | try { 18 | ideaTerminalConsoleService.executeCommand(FLUTTER_GEN_COMMAND) 19 | } catch (e: Exception) { 20 | throw ExecuteGenCommandProcessServiceException("Could not execute '$FLUTTER_GEN_COMMAND' in console. You might need to do it yourself. Is there any terminal tab open?", e) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/presentation/validation/TranslationClientSettingsValidator.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.presentation.validation 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.infrastructure.model.UserSettings 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository.TranslationCredentialsServiceRepository 5 | 6 | class TranslationClientSettingsValidator( 7 | private val credentialsServiceRepository: TranslationCredentialsServiceRepository, 8 | ) { 9 | /** 10 | * whether or not the settings are valid in general, i.e. anything is set 11 | * not yet checking for plausibility, i.e. whether the config is actually identical to the flutter gen config 12 | * 13 | * TODO would be nice, if we had some kind of cooler validation here, would a validation framework be nice here? 14 | * I'm not the first one encountering the issue, but never used it in this kind of way, guess I need some kind of 15 | * "DTO abstraction?" 16 | */ 17 | fun valid(): List { 18 | return when (credentialsServiceRepository.getKey()) { 19 | null -> listOf("No key set") 20 | else -> listOf() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/setup/domain/service/UserInstallDialogs.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.setup.domain.service 2 | 3 | /** 4 | * Defines the dialogs shown to the user during the setup process. 5 | */ 6 | interface UserInstallDialogs { 7 | fun showDiff(title: String, before: String, after: String): String? 8 | fun confirmLibraryInstallation(diffContent: String): String? 9 | fun confirmL10nConfiguration(config: String): Boolean 10 | fun confirmProjectFileModification(diffContent: String): String? 11 | fun selectAppFile(files: List): String? 12 | fun showInfo(title: String, message: String) 13 | 14 | /** 15 | * Presents multiple proposed file modifications (original vs modified) and allows the user 16 | * to select which referenced items should be applied. Returns the list of selected references 17 | * (the original "reference" objects) or null if the user cancelled. 18 | */ 19 | fun confirmProjectFileModifications(changes: List): List? 20 | } 21 | 22 | /** Represents a single file/reference change (original and modified content). */ 23 | data class ProjectFileChange( 24 | val reference: Any, 25 | val original: String, 26 | val modified: String 27 | ) 28 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/project/ProjectSetupStateService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.project 2 | 3 | import com.intellij.openapi.components.PersistentStateComponent 4 | import com.intellij.openapi.components.State 5 | import com.intellij.openapi.components.Storage 6 | import com.intellij.openapi.project.Project 7 | 8 | @State(name = "GptHelperProjectSetup", storages = [Storage("gpt-helper.xml")]) 9 | class ProjectSetupStateService(private val project: Project) : PersistentStateComponent { 10 | 11 | data class State(var asked: Boolean = false) 12 | 13 | private var state: State? = null 14 | 15 | override fun getState(): State { 16 | if (state == null) state = State() 17 | return state!! 18 | } 19 | 20 | override fun loadState(state: State) { 21 | this.state = state 22 | } 23 | 24 | fun hasAsked(): Boolean = state?.asked ?: false 25 | 26 | fun setAsked(value: Boolean) { 27 | if (state == null) state = State() 28 | state!!.asked = value 29 | } 30 | 31 | companion object { 32 | fun getInstance(project: Project): ProjectSetupStateService { 33 | return project.getService(ProjectSetupStateService::class.java) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/infrastructure/factories/TranslationRequestFactoryImpl.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.infrastructure.factories 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.client.FileTranslationRequest 4 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.entity.TranslateFileContext 5 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.factories.TranslationRequestFactory 6 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.presentation.service.IdeaTargetLanguageProvider 7 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository.TranslationFileRepository 8 | 9 | class TranslationRequestFactoryImpl( 10 | private val fileRepository: TranslationFileRepository, 11 | ) : TranslationRequestFactory { 12 | override fun createRequest(translateFileContext: TranslateFileContext): FileTranslationRequest { 13 | val file = fileRepository.getTranslationFileByLanguage(translateFileContext.baseLanguage) 14 | return FileTranslationRequest( 15 | content = file.content, 16 | targetLanguage = translateFileContext.targetLanguage, 17 | baseLanguage = translateFileContext.baseLanguage, 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/entity/TranslationContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.presentation.service.TranslationTaskBackgroundProgress 4 | 5 | /** 6 | * contains information for a single translation 7 | * @param uuid the id of the translation, unique, used for identification - is not persisted, a normal uuid is enough 8 | * @param taskAmountHandled the amount of tasks that have been handled 9 | */ 10 | class TranslationContext( 11 | val uuid: String, 12 | override var progressText: String, 13 | var taskAmount: Int, 14 | var translationRequest: UserTranslationRequest?, 15 | var taskAmountHandled: Int, 16 | var finished: Boolean = false, 17 | var cancelled: Boolean = false, 18 | var changeTranslationContext: ChangeTranslationContext? = null 19 | ) : TranslationTaskBackgroundProgress.TranslationProgressContext { 20 | 21 | override fun getId(): String { 22 | return uuid 23 | } 24 | override fun isFinished(): Boolean { 25 | return finished 26 | } 27 | 28 | override fun isCancelled(): Boolean { 29 | return cancelled 30 | } 31 | 32 | override fun setCancelled() { 33 | cancelled = true 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/missingtranslations/infrastructure/service/MissingTranslationInputServiceIdea.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.missingtranslations.infrastructure.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.entity.MissingTranslationFilteredTargetTranslation 4 | import de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.entity.MissingTranslationAndExistingTranslation 5 | import de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.service.MissingTranslationInputService 6 | import java.util.UUID 7 | 8 | class MissingTranslationInputServiceIdea : MissingTranslationInputService { 9 | override fun collectMissingTranslationInput(missingTranslationAndExistingTranslations: List): List { 10 | //we need to collect the missing translations from the user 11 | //for now we just assume that the user wants to translate to all languages 12 | return missingTranslationAndExistingTranslations.map { 13 | MissingTranslationFilteredTargetTranslation( 14 | it, 15 | it.missingTranslation.languagesMissing, 16 | uuid = UUID.randomUUID().toString() 17 | ) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/presentation/service/IdeaTargetLanguageProvider.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.presentation.service 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.infrastructure.service.TargetLanguageProvider 5 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.presentation.widgets.TargetLanguageDialog 6 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 7 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.parser.ArbFilenameParser 8 | import de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.repository.LanguageFileFinder 9 | 10 | class IdeaTargetLanguageProvider( 11 | private val arbFilenameParser: ArbFilenameParser 12 | ) : TargetLanguageProvider { 13 | override fun getTargetLanguage(): Language? { 14 | var lang: Language? = null 15 | ApplicationManager.getApplication().invokeAndWait { 16 | val dialog = TargetLanguageDialog() 17 | val closedWithOk = dialog 18 | .showAndGet() 19 | if (closedWithOk) { 20 | lang = arbFilenameParser.stringToLanguage(dialog.getTargetLanguage()) 21 | } 22 | } 23 | return lang 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/missingtranslations/domain/entity/MissingTranslationContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.entity 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.presentation.service.TranslationTaskBackgroundProgress 4 | 5 | /** 6 | * @param uuid unique identifier for this context 7 | */ 8 | class MissingTranslationContext( 9 | val uuid: String, 10 | val missingTranslations: List, 11 | var missingTranslationFilteredTargetTranslations: List? = null, 12 | var reference: T, 13 | var finished: Boolean = false, 14 | var taskAmount: Int = Integer.MAX_VALUE, 15 | var finishedTasks: Int = 0, 16 | var cancelled: Boolean = false, 17 | var translationsWithIssues: List? = null, 18 | ) : TranslationTaskBackgroundProgress.TranslationProgressContext { 19 | 20 | override fun getId(): String { 21 | return uuid 22 | } 23 | 24 | override fun isFinished(): Boolean { 25 | return finished 26 | } 27 | 28 | override var progressText: String = "" 29 | 30 | override fun isCancelled(): Boolean { 31 | return cancelled 32 | } 33 | 34 | override fun setCancelled() { 35 | cancelled = true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/setup/domain/service/AppReferenceProvider.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.setup.domain.service 2 | 3 | /** 4 | * Abstraktion für das Finden und Modifizieren von App-Referenzen (MaterialApp/CupertinoApp). 5 | * Die Referenz ist ein generisches Object, die konkrete Implementierung kann z.B. PsiElement verwenden. 6 | */ 7 | interface AppReferenceProvider { 8 | /** 9 | * Sucht alle MaterialApp- und CupertinoApp-Referenzen in .dart-Dateien unterhalb von lib. 10 | * @return Liste von Referenz-Objekten (z.B. PsiElement) 11 | */ 12 | fun findAppReferences(): List 13 | 14 | /** 15 | * Modifiziert die gegebene App-Referenz, um Lokalisierung zu aktivieren. 16 | * @param reference Die zu modifizierende Referenz (z.B. PsiElement) 17 | * @return true, wenn erfolgreich 18 | */ 19 | fun enableLocalization(reference: Any): Boolean 20 | 21 | fun enableLocalizationOnDummy(reference: Any): String? 22 | 23 | fun getContent(reference: Any): String? 24 | 25 | fun modifyFileContent(reference: Any, newContent: String) 26 | 27 | /** 28 | * Prüft, ob die gegebene Referenz bereits die für Lokalisierung benötigten Argumente enthält. 29 | * Wird von SetupService.isProjectCombined verwendet. 30 | */ 31 | fun referenceHasLocalization(reference: Any): Boolean 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/repository/IdeaTranslationCredentialsServiceRepository.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.repository 2 | 3 | import com.intellij.credentialStore.CredentialAttributes 4 | import com.intellij.credentialStore.Credentials 5 | import com.intellij.credentialStore.generateServiceName 6 | import com.intellij.ide.passwordSafe.PasswordSafe 7 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository.TranslationCredentialsServiceRepository 8 | 9 | 10 | class IdeaTranslationCredentialsServiceRepository : TranslationCredentialsServiceRepository { 11 | 12 | override fun persistKey(key: String) { 13 | val credentialAttributes = createCredentialAttributes() 14 | PasswordSafe.instance.set(credentialAttributes, Credentials("", key)) 15 | } 16 | 17 | override fun getKey(): String? { 18 | val credentialAttributes = createCredentialAttributes() 19 | return PasswordSafe.instance.get(credentialAttributes)?.getPasswordAsString() 20 | } 21 | 22 | override fun hasPassword(): Boolean { 23 | return getKey() != null 24 | } 25 | 26 | private fun createCredentialAttributes(): CredentialAttributes { 27 | return CredentialAttributes( 28 | serviceName = generateServiceName("openai", "apikey"), 29 | ) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/service/JsonChunkMerger.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service 2 | 3 | import com.fasterxml.jackson.core.util.DefaultIndenter 4 | import com.fasterxml.jackson.core.util.DefaultPrettyPrinter 5 | import com.fasterxml.jackson.databind.ObjectMapper 6 | import com.fasterxml.jackson.databind.node.ObjectNode 7 | 8 | class JsonChunkMerger(private val objectMapper: ObjectMapper) { 9 | private val prettyPrinter = DefaultPrettyPrinter().withObjectIndenter(DefaultIndenter().withLinefeed("\n")) 10 | fun mergeChunks(jsonChunks: List): String { 11 | val mergedNode = objectMapper.createObjectNode() 12 | 13 | jsonChunks.forEach { jsonChunk -> 14 | //if not surrounded by {}, then append it 15 | val sanitizedChunk = sanitizeChunk(jsonChunk) 16 | val chunkNode = objectMapper.readTree(sanitizedChunk) 17 | chunkNode.fields().forEach { (key, value) -> 18 | mergedNode.set(key, value) 19 | } 20 | } 21 | 22 | //does not allow \r\n 23 | return objectMapper.writer(prettyPrinter) 24 | .writeValueAsString(mergedNode) 25 | } 26 | 27 | private fun sanitizeChunk(jsonChunk: String) = if (!jsonChunk.startsWith("{")) { 28 | "{$jsonChunk}" 29 | } else { 30 | jsonChunk 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/TranslateKeyTranslationTriggeredHooks.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.infrastructure.service.FlutterArbCurrentFileModificationService 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Translation 5 | import de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service.FlutterGenCommandProcessService 6 | 7 | /** 8 | * implements the domain service for the external translation process 9 | */ 10 | class TranslateKeyTranslationTriggeredHooks( 11 | private val flutterGenCommandProcessService: ExternalTranslationProcessService, 12 | private val currentFileModificationService: FlutterArbCurrentFileModificationService 13 | ) : TranslationTriggeredHooks { 14 | override fun translationTriggered(translation: Translation) { 15 | translationTriggeredPartial(translation) 16 | translationTriggeredPostTranslation() 17 | } 18 | 19 | override fun translationTriggeredPostTranslation() { 20 | flutterGenCommandProcessService.postTranslationProcess() 21 | } 22 | 23 | override fun translationTriggeredPartial(translation: Translation) { 24 | currentFileModificationService.modifyCurrentFile(translation) 25 | } 26 | 27 | override fun translationTriggeredInit() { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/psiutils/DartAdditiveExpressionExtractor.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.psiutils 2 | 3 | import com.intellij.psi.PsiElement 4 | import com.jetbrains.lang.dart.psi.DartAdditiveExpression 5 | import com.jetbrains.lang.dart.psi.DartStringLiteralExpression 6 | 7 | class DartAdditiveExpressionExtractor { 8 | /** 9 | * find all additions in the string to the topmost additive expression 10 | * so that we can find Strings of type "hello" + 3 + "world" 11 | */ 12 | fun extractAdditiveExpression(dartStringLiteral: DartStringLiteralExpression): DartAdditiveExpression? { 13 | var parent = dartStringLiteral.parent 14 | 15 | // Climb up to the topmost additive expression 16 | while (parent?.parent is DartAdditiveExpression) { 17 | parent = parent.parent 18 | } 19 | 20 | return if (parent is DartAdditiveExpression) { 21 | parent 22 | } else { 23 | null 24 | } 25 | } 26 | 27 | fun isDescendantOf(stringLiteral: DartStringLiteralExpression, additiveExpression: DartAdditiveExpression): Boolean { 28 | var parent: PsiElement? = stringLiteral.parent 29 | while (parent != null) { 30 | if (parent == additiveExpression) { 31 | return true 32 | } 33 | parent = parent.parent 34 | } 35 | return false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/presentation/validation/FlutterIntlValidator.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.presentation.validation 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.infrastructure.model.UserSettings 4 | 5 | class FlutterIntlValidator { 6 | /** 7 | * whether or not the settings are valid in general, i.e. anything is set 8 | * not yet checking for plausibility, i.e. whether the config is actually identical to the flutter gen config 9 | * 10 | * TODO would be nice, if we had some kind of cooler validation here, would a validation framework be nice here? 11 | * I'm not the first one encountering the issue, but never used it in this kind of way, guess I need some kind of 12 | * "DTO abstraction?" 13 | */ 14 | fun valid(userSettings: UserSettings): List { 15 | val errors = mutableListOf() 16 | if (userSettings.outputClass == "") { 17 | errors.add("Output class not set") 18 | } 19 | if (userSettings.arbDir == "") { 20 | errors.add("Arb dir not set") 21 | } 22 | if (userSettings.templateArbFile == "") { 23 | errors.add("Template arb file not set") 24 | } 25 | if (userSettings.outputLocalizationFile == "") { 26 | errors.add("Output localization file not set") 27 | } 28 | return errors 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/infrastructure/service/ArbFlutterPartialFileTranslationResponseHandler.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.infrastructure.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.client.PartialFileTranslationResponse 4 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.entity.TranslateFileContext 5 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.service.PartialFileResponseHandler 6 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository.TranslationFileRepository 7 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.service.FormatTranslationFileContentService 8 | 9 | class ArbFlutterPartialFileTranslationResponseHandler( 10 | private val arbFileContentModificationService: ArbFileContentModificationService, 11 | private val translationFileRepository: TranslationFileRepository, 12 | ) : PartialFileResponseHandler { 13 | override fun handlePartialFileResponse(context: TranslateFileContext, partialFileTranslationResponse: PartialFileTranslationResponse) { 14 | var file = translationFileRepository.createOrGetTranslationFileByLanguage(context.targetLanguage) 15 | file = arbFileContentModificationService.appendTranslation(file, partialFileTranslationResponse.entry) 16 | translationFileRepository.saveTranslationFile(file) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/entity/MultiKeyTranslationContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.SimpleTranslationEntry 5 | import de.keeyzar.gpthelper.gpthelper.features.translations.presentation.service.TranslationTaskBackgroundProgress 6 | 7 | data class MultiKeyTranslationContext( 8 | /** 9 | * which entries must be modified? We do not need any id anymore whatsoever, because we have keys already 10 | */ 11 | val translationEntries: List, 12 | val baseLanguage: Language, 13 | val targetLanguages: List, 14 | val uuid: String, 15 | var finished: Boolean = false, 16 | var taskAmount: Int = Integer.MAX_VALUE, 17 | var finishedTasks: Int = 0, 18 | var cancelled: Boolean = false, 19 | ) : TranslationTaskBackgroundProgress.TranslationProgressContext { 20 | override fun getId(): String { 21 | return uuid 22 | } 23 | 24 | override fun isFinished(): Boolean { 25 | return finished 26 | } 27 | 28 | override var progressText: String = "" 29 | 30 | override fun isCancelled(): Boolean { 31 | return cancelled 32 | } 33 | 34 | override fun setCancelled() { 35 | cancelled = true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/keeyzar/gpthelper/MyPluginTest.kt: -------------------------------------------------------------------------------- 1 | //package com.github.keeyzar.gpthelper 2 | // 3 | //import com.intellij.ide.highlighter.XmlFileType 4 | //import com.intellij.openapi.components.service 5 | //import com.intellij.psi.xml.XmlFile 6 | //import com.intellij.testFramework.TestDataPath 7 | //import com.intellij.testFramework.fixtures.BasePlatformTestCase 8 | //import com.intellij.util.PsiErrorElementUtil 9 | // 10 | //@TestDataPath("\$CONTENT_ROOT/src/test/testData") 11 | //class MyPluginTest : BasePlatformTestCase() { 12 | // 13 | // fun testXMLFile() { 14 | // val psiFile = myFixture.configureByText(XmlFileType.INSTANCE, "bar") 15 | // val xmlFile = assertInstanceOf(psiFile, XmlFile::class.java) 16 | // 17 | // assertFalse(PsiErrorElementUtil.hasErrors(project, xmlFile.virtualFile)) 18 | // 19 | // assertNotNull(xmlFile.rootTag) 20 | // 21 | // xmlFile.rootTag?.let { 22 | // assertEquals("foo", it.name) 23 | // assertEquals("bar", it.value.text) 24 | // } 25 | // } 26 | // 27 | // fun testRename() { 28 | // myFixture.testRename("foo.xml", "foo_after.xml", "a2") 29 | // } 30 | // 31 | // fun testProjectService() { 32 | // val projectService = project.service() 33 | // 34 | // assertNotSame(projectService.getRandomNumber(), projectService.getRandomNumber()) 35 | // } 36 | // 37 | // override fun getTestDataPath() = "src/test/testData/rename" 38 | //} 39 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/client/DDDTranslationRequestClient.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.client 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.client.FileTranslationRequest 4 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.client.PartialFileTranslationResponse 5 | 6 | interface DDDTranslationRequestClient { 7 | suspend fun translateValueOnly(clientTranslationRequest: ClientTranslationRequest, partialTranslationResponse: PartialTranslationResponse, isCancelled: () -> Boolean, partialTranslationFinishedCallback: (PartialTranslationResponse) -> Unit) 8 | suspend fun createComplexArbEntry(clientTranslationRequest: ClientTranslationRequest) : PartialTranslationResponse 9 | 10 | // Batch methods for improved performance 11 | suspend fun createComplexArbEntryBatch(batchRequest: BatchClientTranslationRequest, isCancelled: () -> Boolean, progressReport: () -> Unit): BatchPartialTranslationResponse 12 | suspend fun translateValueOnlyBatch(batchRequest: BatchClientTranslationRequest, batchResponse: BatchPartialTranslationResponse, isCancelled: () -> Boolean, partialTranslationFinishedCallback: (PartialTranslationResponse) -> Unit) 13 | 14 | suspend fun translate( 15 | fileTranslationRequest: FileTranslationRequest, 16 | partialTranslationFinishedCallback: (PartialFileTranslationResponse, taskSize: Int) -> Unit 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/shared/infrastructure/utils/ObjectMapperProvider.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.shared.infrastructure.utils 2 | 3 | import com.fasterxml.jackson.core.util.DefaultIndenter 4 | import com.fasterxml.jackson.core.util.DefaultPrettyPrinter 5 | import com.fasterxml.jackson.databind.ObjectMapper 6 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory 7 | import com.fasterxml.jackson.module.kotlin.KotlinFeature 8 | import com.fasterxml.jackson.module.kotlin.KotlinModule 9 | 10 | class ObjectMapperProvider() { 11 | fun provideObjectMapper(yamlFactory: YAMLFactory?): ObjectMapper { 12 | val prettyPrinter = DefaultPrettyPrinter().withObjectIndenter(DefaultIndenter().withLinefeed("\n")) 13 | 14 | var objectMapper = when(yamlFactory) { 15 | null -> ObjectMapper() 16 | else -> ObjectMapper(yamlFactory) 17 | } 18 | objectMapper = objectMapper.registerModule( 19 | KotlinModule.Builder() 20 | .withReflectionCacheSize(512) 21 | .configure(KotlinFeature.NullToEmptyCollection, false) 22 | .configure(KotlinFeature.NullToEmptyMap, false) 23 | .configure(KotlinFeature.NullIsSameAsDefault, false) 24 | .configure(KotlinFeature.StrictNullChecks, false) 25 | .build() 26 | ) 27 | objectMapper.setDefaultPrettyPrinter(prettyPrinter) 28 | return objectMapper 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/review/infrastructure/service/IdeaOpenPageService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.review.infrastructure.service 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.ui.DialogBuilder 5 | import com.intellij.ui.dsl.builder.panel 6 | import de.keeyzar.gpthelper.gpthelper.features.review.domain.service.OpenPageService 7 | import java.awt.Desktop 8 | import java.net.URI 9 | 10 | class IdeaOpenPageService : OpenPageService { 11 | override fun openPage(link: URI) { 12 | if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { 13 | val desktop = Desktop.getDesktop() 14 | desktop.browse(link) 15 | } else { 16 | showErrorDialog(link) 17 | } 18 | } 19 | 20 | private fun showErrorDialog(link: URI) { 21 | ApplicationManager.getApplication().invokeAndWait { 22 | val builder = DialogBuilder() 23 | builder.setTitle("Could Not Open Review Page...") 24 | builder.addOkAction() 25 | builder.setCenterPanel(panel { 26 | row { 27 | label( 28 | "Could not open review page, please open it manually:
" + 29 | "${link}" 30 | ) 31 | } 32 | }) 33 | builder.show() 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/shared/domain/service/ThreadingService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.shared.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.TranslationContext 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.runBlocking 6 | import kotlinx.coroutines.withContext 7 | import java.util.concurrent.LinkedBlockingQueue 8 | 9 | class ThreadingService( 10 | private val queue: LinkedBlockingQueue = LinkedBlockingQueue(), 11 | private var initialized: Boolean = false, 12 | ) { 13 | 14 | suspend fun putIntoQueue(elem: T) { 15 | withContext(Dispatchers.IO) { 16 | queue.put(elem) 17 | } 18 | } 19 | 20 | fun startQueueIfNotRunning(handler: suspend (T) -> Unit) { 21 | if (initialized) { 22 | return 23 | } 24 | initialized = true 25 | 26 | // Create a background thread to process the queue 27 | val backgroundThread = Thread { 28 | while (true) { 29 | val item = queue.take() // Blocks until an item is available 30 | runBlocking { 31 | //we run in blocking context, because it's not required to run in parallel. it's fine to run sequentially, 32 | //but we might do so in the future 33 | handler.invoke(item) 34 | } 35 | } 36 | } 37 | backgroundThread.start() 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/service/IdeaGatherFileTranslationContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.entity.TranslateFileContext 4 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.service.GatherFileTranslationContext 5 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.infrastructure.service.TargetLanguageProvider 6 | import de.keeyzar.gpthelper.gpthelper.features.flutter_intl.infrastructure.service.ArbFilesService 7 | import java.util.UUID 8 | 9 | class IdeaGatherFileTranslationContext( 10 | private val contextProvider: ContextProvider, 11 | private val targetLanguageProvider: TargetLanguageProvider, 12 | private val arbFilesService: ArbFilesService, 13 | ) : GatherFileTranslationContext { 14 | override fun gatherTranslationContext(uuid: UUID): TranslateFileContext? { 15 | val context = contextProvider.getTranslateWholeFileContext(uuid)?: throw IllegalStateException("Context not found, this is a programmer error, sorry") 16 | val targetLang = targetLanguageProvider.getTargetLanguage()?: return null 17 | return TranslateFileContext( 18 | baseLanguage = arbFilesService.getBaseLanguage(context.baseFile.virtualFile.toNioPath()), 19 | targetLanguage = targetLang, 20 | context.baseFile.virtualFile.toNioPath() 21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/flutter_intl/infrastructure/repository/FlutterFileRepository.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.flutter_intl.infrastructure.repository 2 | 3 | import com.intellij.openapi.application.ReadAction 4 | import com.intellij.openapi.editor.Document 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.openapi.vfs.VirtualFileManager 7 | import com.intellij.psi.PsiDocumentManager 8 | import com.intellij.psi.PsiManager 9 | import de.keeyzar.gpthelper.gpthelper.features.flutter_intl.domain.exceptions.FlutterIntlFileNotFound 10 | import java.nio.file.Path 11 | 12 | /** 13 | * get the file, externalized for testing 14 | */ 15 | class FlutterFileRepository { 16 | fun getFileContent(project: Project, filePath: Path): String { 17 | return ReadAction.compute { 18 | val virtualFile = VirtualFileManager.getInstance().findFileByNioPath(filePath) 19 | ?: throw FlutterIntlFileNotFound("File not found at $filePath") 20 | val psiFile = PsiManager.getInstance(project).findFile(virtualFile) 21 | ?: throw FlutterIntlFileNotFound("We found a file, but it might be a directory or not a valid file at $filePath") 22 | return@compute PsiDocumentManager.getInstance(project).getDocument(psiFile)?.text 23 | ?: throw FlutterIntlFileNotFound("File found, but could not proceed, because the file is e.g. binary or there is no associated document at $filePath") 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/client/OpenAIClientConnectionTester.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.client 2 | 3 | import com.google.genai.types.ListModelsConfig 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.client.ClientConnectionTester 5 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions.TranslationClientConnectionException 6 | import de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.configuration.LLMConfigProvider 7 | 8 | class OpenAIClientConnectionTester( 9 | private val configProvider: LLMConfigProvider 10 | ) : ClientConnectionTester { 11 | /** 12 | * there might be other issues. We should allow the user to see the issue, therefore return the throwable 13 | */ 14 | override suspend fun testClientConnection(key: String): Throwable? { 15 | //like try with 16 | configProvider.withKeyGemini(key).use { 17 | try { 18 | //if this call is successful, everything is fine. we can save some resources 19 | //by using some specific model, but no time currently in a train. 20 | it.models.list(ListModelsConfig.builder().pageSize(20).build()).page() 21 | return null 22 | } catch (e: Throwable) { 23 | e.printStackTrace() 24 | return TranslationClientConnectionException("Could not connect", e) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/parser/UserTranslationInputParser.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.parser 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.* 4 | 5 | 6 | /** 7 | * parses the user translation input 8 | */ 9 | class UserTranslationInputParser( 10 | private val parser: ArbFilenameParser, 11 | ) { 12 | fun toUserTranslationRequest(baseLanguage: Language, userTranslationInput: UserTranslationInput): UserTranslationRequest { 13 | return UserTranslationRequest( 14 | targetLanguages = toTargetLanguages(userTranslationInput), 15 | baseTranslation = toTranslation(baseLanguage, userTranslationInput) 16 | ); 17 | } 18 | 19 | private fun toTargetLanguages(userTranslationInput: UserTranslationInput) = 20 | userTranslationInput.languagesToTranslate 21 | .filter { it.value } 22 | .map { parser.stringToLanguage(it.key) } 23 | 24 | private fun toTranslation(baseLanguage: Language, userTranslationInput: UserTranslationInput): Translation { 25 | return Translation( 26 | lang = baseLanguage, 27 | entry = SimpleTranslationEntry( 28 | //TODO WHERE DO I GET THE ID FROM? 29 | id = null, 30 | desiredKey = userTranslationInput.desiredKey, 31 | desiredValue = userTranslationInput.desiredValue, 32 | desiredDescription = userTranslationInput.desiredDescription, 33 | ) 34 | ); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/service/ContextProvider.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.infrastructure.model.AutoLocalizeContext 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.model.TranslateWholeFileContext 5 | import java.util.* 6 | 7 | /** 8 | * TODO this is an issue, I need to fix... I need some kind of map at least 9 | */ 10 | class ContextProvider { 11 | private val wholeFileContext: MutableMap = mutableMapOf() 12 | private val autoLocalizeContext: MutableMap = mutableMapOf() 13 | fun putTranslateWholeFileContext(uuid: UUID, translateWholeFileContext: TranslateWholeFileContext) { 14 | wholeFileContext.putIfAbsent(uuid, translateWholeFileContext); 15 | } 16 | 17 | fun getTranslateWholeFileContext(uuid: UUID): TranslateWholeFileContext? { 18 | return wholeFileContext[uuid] 19 | } 20 | 21 | fun removeWholeFileContext(uuid: UUID) { 22 | wholeFileContext.remove(uuid) 23 | } 24 | 25 | fun putAutoLocalizeContext(uuid: UUID, autoLocalizeContext: AutoLocalizeContext) { 26 | this.autoLocalizeContext.putIfAbsent(uuid, autoLocalizeContext); 27 | } 28 | 29 | fun getAutoLocalizeContext(uuid: UUID): AutoLocalizeContext? { 30 | return autoLocalizeContext[uuid] 31 | } 32 | 33 | fun removeAutoLocalizeContext(uuid: UUID) { 34 | autoLocalizeContext.remove(uuid) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/infrastructure/service/FlutterArbTranslateFileFinished.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.infrastructure.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.entity.TranslateFileContext 4 | import de.keeyzar.gpthelper.gpthelper.features.filetranslation.domain.service.FinishedFileTranslationHandler 5 | import de.keeyzar.gpthelper.gpthelper.features.shared.infrastructure.utils.JsonUtils 6 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository.TranslationFileRepository 7 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.service.FormatTranslationFileContentService 8 | 9 | class FlutterArbTranslateFileFinished( 10 | private val translationFileRepository: TranslationFileRepository, 11 | private val formatTranslationFileContentService: FormatTranslationFileContentService, 12 | private val jsonUtils: JsonUtils, 13 | ) : FinishedFileTranslationHandler { 14 | override fun finishedTranslation(context: TranslateFileContext) { 15 | //get base language as string 16 | val base = translationFileRepository.getTranslationFileByLanguage(context.baseLanguage) 17 | val target = translationFileRepository.getTranslationFileByLanguage(context.targetLanguage) 18 | val newTargetString = jsonUtils.reorderJson(base.content, target.content) 19 | var newTarget = target.copy(content = newTargetString) 20 | newTarget = formatTranslationFileContentService.formatTranslationFileContent(newTarget) 21 | translationFileRepository.saveTranslationFile(newTarget) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/shared/presentation/Initializer.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.shared.presentation 2 | 3 | import com.intellij.openapi.project.Project 4 | import de.keeyzar.gpthelper.gpthelper.features.flutter_intl.domain.repository.FlutterIntlSettingsRepository 5 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.client.ClientConnectionTester 6 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.client.GPTModelProvider 7 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository.TranslationCredentialsServiceRepository 8 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository.UserSettingsRepository 9 | import de.keeyzar.gpthelper.gpthelper.project.ProjectKoinService 10 | 11 | class Initializer( 12 | val userSettingsRepository: UserSettingsRepository, 13 | val flutterIntlSettingsRepository: FlutterIntlSettingsRepository, 14 | val connectionTester: ClientConnectionTester, 15 | val credentialsServiceRepository: TranslationCredentialsServiceRepository, 16 | val gptModelProvider: GPTModelProvider 17 | ) { 18 | companion object { 19 | fun create(project: Project): Initializer { 20 | val koin = ProjectKoinService.getInstance(project).getKoin() 21 | return Initializer( 22 | userSettingsRepository = koin.get(), 23 | flutterIntlSettingsRepository = koin.get(), 24 | connectionTester = koin.get(), 25 | credentialsServiceRepository = koin.get(), 26 | gptModelProvider = koin.get() 27 | ) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/parser/ArbFilenameParser.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.parser 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 4 | import java.nio.file.Path 5 | 6 | /** 7 | * different parsing functions for arb filenames, based from different points of view 8 | */ 9 | class ArbFilenameParser { 10 | fun stringToLanguage(langAsString: String): Language { 11 | //it's either en_US or en / de_DE or de / ... 12 | val split = langAsString.split("_") 13 | return if (split.size == 1) { 14 | Language(split[0], null) 15 | } else { 16 | Language(split[0], split[1]) 17 | } 18 | } 19 | 20 | /** 21 | * returns the prefix of the arb file, either finds en_US or en in a file named app_en_US.arb / app_en.arb 22 | */ 23 | fun getArbFilePrefix(filename: String): String { 24 | val regex = Regex("^(.*?)([a-z]{2}(?:_[A-Z]{2})?)?(\\.arb)$") 25 | val matchResult = regex.matchEntire(filename) 26 | return matchResult?.groups?.get(1)?.value ?: "" 27 | } 28 | 29 | fun getLanguageFromFilename(filename: String): Language { 30 | val languageString = filename.replace(getArbFilePrefix(filename), "").replace(".arb", "") 31 | return stringToLanguage(languageString) 32 | } 33 | fun getLanguageFromPath(filePath: Path): Language { 34 | val filename = filePath.fileName.toString() 35 | val languageString = filename.replace(getArbFilePrefix(filename), "").replace(".arb", "") 36 | return stringToLanguage(languageString) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html 2 | 3 | pluginGroup = com.github.keeyzar.gpthelper 4 | pluginName = gpt-helper 5 | pluginRepositoryUrl = https://github.com/keeyzar/gpt-helper 6 | # SemVer format -> https://semver.org 7 | pluginVersion = 2.0.13 8 | 9 | # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html 10 | pluginSinceBuild = 243 11 | 12 | # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension 13 | platformType = IC 14 | platformVersion = 2024.3.6 15 | 16 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html 17 | # Example: platformPlugins = com.jetbrains.php:203.4449.22, org.intellij.scala:2023.3.27@EAP 18 | platformPlugins = Dart:243.23177 19 | # Example: platformBundledPlugins = com.intellij.java 20 | platformBundledPlugins = com.intellij.modules.json, org.jetbrains.plugins.terminal, org.jetbrains.plugins.yaml 21 | # Example: platformBundledModules = intellij.spellchecker 22 | platformBundledModules = 23 | 24 | # Gradle Releases -> https://github.com/gradle/gradle/releases 25 | gradleVersion = 9.0.0 26 | 27 | # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib 28 | kotlin.stdlib.default.dependency = true 29 | 30 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html 31 | org.gradle.configuration-cache = true 32 | 33 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html 34 | org.gradle.caching = true 35 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/shared/infrastructure/model/UserSettings.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.shared.infrastructure.model 2 | 3 | data class UserSettings( 4 | /** 5 | * where is the directory containing the arb files 6 | */ 7 | val arbDir: String?, 8 | /** 9 | * I might need to remove the key here, because it should not be visible everywhere, where we need access to the userSettings 10 | */ 11 | val outputClass: String, 12 | /** 13 | * whether we're working with a nullable getter 14 | */ 15 | val nullableGetter: Boolean, 16 | 17 | /** 18 | * base file for the translations, i.e. here you can find the prefix 19 | */ 20 | val templateArbFile: String, 21 | val intlConfigFile: String?, 22 | val watchIntlConfigFile: Boolean = true, 23 | /** 24 | * The import statement for the generated localization class 25 | */ 26 | val flutterImportStatement: String, 27 | /** 28 | * here you can find the file, where the outputClass is located in 29 | */ 30 | val outputLocalizationFile: String, 31 | /** 32 | * how many requests should be done in parallel 33 | */ 34 | val parallelism: Int, 35 | /** 36 | * the tonality of the voice (i.e. formal, informal) 37 | */ 38 | val tonality: String, 39 | /** 40 | * which model should be used? 41 | */ 42 | val gptModel: String, 43 | /** 44 | * whether to use the new advanced arb key translation 45 | */ 46 | val translateAdvancedArbKeys: Boolean, 47 | val maxTranslationHistory: Int, 48 | ) { 49 | 50 | fun getArbFilePrefix(): String { 51 | val regex = Regex("^(.*?)([a-z]{2}(?:_[A-Z]{2})?)?(\\.arb)$") 52 | val matchResult = regex.matchEntire(templateArbFile) 53 | return matchResult?.groups?.get(1)?.value ?: "" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/domain/service/TranslationPreprocessor.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.domain.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.TranslateKeyContext 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.TranslationContext 5 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.UserTranslationInput 6 | 7 | class TranslationPreprocessor( 8 | private val verifyTranslationSettingsService: VerifyTranslationSettingsService, 9 | private val gatherTranslationContextService: GatherTranslationContextService, 10 | private val gatherUserInputService: GatherUserInputService 11 | ) { 12 | /** 13 | * preprocess the request. 14 | * @return true, if preprocessing was successful, false otherwise 15 | */ 16 | fun preprocess(translationContext: TranslationContext) : Pair? { 17 | //verify settings are set 18 | val verified = verifyTranslationSettingsService.verifySettingsAndInformUserIfInvalid() 19 | if (!verified) { 20 | println("settings are not verified, please set them") 21 | return null 22 | } 23 | 24 | val statement = translationContext.changeTranslationContext?.value 25 | val translateKeyContext = gatherTranslationContextService.gatherTranslationContext(statement) 26 | translateKeyContext.changeTranslationContext = translationContext.changeTranslationContext 27 | 28 | val userTranslationInput = gatherUserInputService.requestInformationFromUser(translateKeyContext) 29 | if (userTranslationInput == null) { 30 | println("user cancelled the process") 31 | return null 32 | } 33 | return Pair(translateKeyContext, userTranslationInput) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/service/JsonFileChunker.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service 2 | 3 | import com.fasterxml.jackson.databind.JsonNode 4 | import com.fasterxml.jackson.databind.ObjectMapper 5 | 6 | class JsonFileChunker( 7 | private val objectMapper: ObjectMapper, 8 | ) { 9 | /** 10 | * TODO it would be nice, if we would recursively check whether a chunk is too big, i.e. > 400 chars, and if yes, split it in half 11 | */ 12 | fun chunkJsonBasedOnTotalStringSize(json: String, chunkSize: Long): List { 13 | // Read the JSON string into a JsonNode object 14 | val jsonNode = objectMapper.readTree(json) 15 | 16 | // Split the JsonNode into chunks 17 | val chunks = mutableListOf>() 18 | var currentChunk = mutableMapOf() 19 | var count = 0L 20 | 21 | jsonNode.fields().forEach { (key, value) -> 22 | val tokens = key.length + value.toString().length // count tokens as keys + values lengths 23 | 24 | // If adding the next key/value pair would make the current chunk exceed the chunk size, add the current chunk to the list of chunks 25 | if (count + tokens > chunkSize && currentChunk.isNotEmpty()) { 26 | chunks.add(currentChunk.toMap()) 27 | currentChunk = mutableMapOf() 28 | count = 0 29 | } 30 | 31 | currentChunk[key] = value 32 | count += tokens 33 | } 34 | 35 | if (currentChunk.isNotEmpty()) { 36 | chunks.add(currentChunk.toMap()) 37 | } 38 | 39 | // Filter out any empty chunks and write each chunk as a string 40 | return chunks 41 | .filter { it.isNotEmpty() } 42 | .map { objectMapper.writeValueAsString(it) } 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/infrastructure/service/ArbFileContentModificationService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.infrastructure.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.infrastructure.utils.JsonUtils 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.FileToTranslate 5 | 6 | class ArbFileContentModificationService(private val jsonUtils: JsonUtils) { 7 | fun appendTranslation(fileToTranslate: FileToTranslate, newContent: Map): FileToTranslate { 8 | val newContentToAppend = jsonUtils.mapToJsonString(newContent) 9 | val newFileContent = appendNewContent(fileToTranslate.content, newContentToAppend) 10 | return fileToTranslate.copy(content = newFileContent); 11 | } 12 | 13 | private fun appendNewContent(fileContent: String, newContent: String): String { 14 | var newFile = findAndReplaceWithExistingEntry(fileContent, newContent) 15 | if (newFile == null) { 16 | newFile = findAndReplaceWithoutEntry(fileContent, newContent) 17 | } 18 | return newFile 19 | } 20 | 21 | private fun findAndReplaceWithoutEntry(fileContent: String, newContent: String): String { 22 | val lastIndex = fileContent.lastIndexOf("}") 23 | return if (lastIndex != -1) { 24 | fileContent.substring(0, lastIndex) + " \n " + newContent + " \n}" 25 | } else { 26 | " {\n $newContent \n}" 27 | } 28 | } 29 | 30 | private fun findAndReplaceWithExistingEntry(fileContent: String, newContent: String): String? { 31 | val hasEntries = jsonUtils.hasAnyEntry(fileContent) 32 | if (!hasEntries) return null; 33 | 34 | val lastIndex = fileContent.lastIndexOf("}") 35 | if (lastIndex != -1) { 36 | return fileContent.substring(0, lastIndex) + ",\n " + newContent + "\n}" 37 | 38 | } 39 | return null 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/filetranslation/presentation/widgets/TargetLanguageDialog.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.filetranslation.presentation.widgets 2 | 3 | import com.intellij.openapi.ui.DialogWrapper 4 | import com.intellij.ui.components.JBPanel 5 | import com.intellij.ui.components.JBTextField 6 | import com.intellij.ui.dsl.builder.Cell 7 | import com.intellij.ui.dsl.builder.Panel 8 | import com.intellij.ui.dsl.builder.bindText 9 | import com.intellij.ui.dsl.builder.panel 10 | import java.awt.Dimension 11 | import javax.swing.JComponent 12 | import javax.swing.JPanel 13 | 14 | class TargetLanguageDialog : DialogWrapper(true) { 15 | private lateinit var textField: Cell 16 | private var model: Model = Model("") 17 | private val regex = """^[a-z]{2}(?:_[A-Z]{2})?$""".toRegex() 18 | init { 19 | title = "Enter Target Language" 20 | setOKButtonText("Translate") 21 | setCancelButtonText("Cancel") 22 | init() 23 | } 24 | 25 | override fun createCenterPanel(): JComponent { 26 | model = Model("") 27 | val panel: JPanel = panel { 28 | row { 29 | label("Please enter the target language, like en_US or en") 30 | textField = textField() 31 | .bindText(model::targetLanguage) 32 | .validationInfo { 33 | if (!regex.containsMatchIn(it.text)) { 34 | error("is not a valid input, please use the format en_US or en") 35 | } else { 36 | null 37 | } 38 | } 39 | } 40 | } 41 | panel.minimumSize = Dimension(200, 50) 42 | return panel 43 | } 44 | 45 | fun getTargetLanguage(): String { 46 | return model.targetLanguage 47 | } 48 | 49 | internal data class Model( 50 | var targetLanguage: String 51 | ) 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/setup/presentation/actions/SetupIntlAction.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.setup.presentation.actions 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent 4 | import com.intellij.openapi.project.Project 5 | import de.keeyzar.gpthelper.gpthelper.features.shared.presentation.actions.ProjectAwareAction 6 | import de.keeyzar.gpthelper.gpthelper.features.translations.presentation.dependencyinjection.FlutterArbTranslationInitializer 7 | 8 | /** 9 | * When a project is started / we check after some seconds (e.g. 5) whether the project is set up for intl 10 | * if not, we show a notification: Do you want to set up intl for your project? 11 | * if he clicks it, we start the intl setup 12 | * - the user can also trigger that via right clicking the project and choosing "set-up intl" 13 | * 14 | * the process is as follows 15 | * 1. check if the user has the correct libs installed, if not, ask if we should install the libs 16 | * 2. check if the user has in root a l10n.yaml configuration, if not, ask if we should create it with default 17 | * parameters 18 | * 3. get the settings and check if the main language (e.g. lib/l10n/en_EN.arb) exists, if not, create file in dir 19 | * 4. search in the whole project for runApp function method call: ask the user: is this your main file 20 | * if the user says yes - we send the file to gemini and ask: how can we integrate l10n here: 21 | * we need delegates, etc. 22 | * 5. show the user: hey, this is the difference (a diff window possibly exists already), do you want to accept? 23 | * 6. that's it, intl is set up 24 | * 25 | * in each step, ask the user, whether he's fine with the changes. We may even add the possibility to use 26 | * Localizations without any context at all - and when we check for buildContext variable, and we find none, we just 27 | * use the no context l10n 28 | */ 29 | class SetupIntlAction : ProjectAwareAction() { 30 | override fun actionPerformed(e: AnActionEvent, project: Project, initializer: FlutterArbTranslationInitializer) { 31 | initializer.setupService.orchestrate() 32 | } 33 | } -------------------------------------------------------------------------------- /src/test/kotlin/features/psiutils/DartAdditiveExpressionExtractorTest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.psiutils 2 | 3 | import com.intellij.psi.util.descendantsOfType 4 | import com.intellij.testFramework.fixtures.BasePlatformTestCase 5 | import com.jetbrains.lang.dart.psi.DartAdditiveExpression 6 | import com.jetbrains.lang.dart.psi.DartStringLiteralExpression 7 | 8 | class DartAdditiveExpressionExtractorTest : BasePlatformTestCase() { 9 | 10 | private lateinit var dartAdditiveExpressionExtractor: DartAdditiveExpressionExtractor 11 | 12 | override fun setUp() { 13 | super.setUp() 14 | dartAdditiveExpressionExtractor = DartAdditiveExpressionExtractor() 15 | myFixture.testDataPath = "src/test/resources" 16 | } 17 | 18 | fun testComplexString() { 19 | val psiFile = myFixture.configureByText("Test.dart", """ 20 | void main() { 21 | String complexString = "hello" + 3 + "world"; 22 | } 23 | """.trimIndent()) 24 | 25 | val stringLiteral = psiFile.descendantsOfType().first() 26 | val extractAdditiveExpression = dartAdditiveExpressionExtractor.extractAdditiveExpression(stringLiteral)?.text 27 | assertEquals("\"hello\" + 3 + \"world\"", extractAdditiveExpression) 28 | } 29 | 30 | 31 | fun testAdditiveExpression2() { 32 | val psiFile = myFixture.configureByFile("psiutils/dart/additive_expr_2.dart") 33 | 34 | val stringLiteral = psiFile.descendantsOfType().first() 35 | val extractAdditiveExpression = dartAdditiveExpressionExtractor.extractAdditiveExpression(stringLiteral)?.text 36 | assertEquals("'nice ' + 'world ' + 'new'", extractAdditiveExpression) 37 | } 38 | 39 | fun testIsDescendantOf_true() { 40 | val psiFile = myFixture.configureByFile("psiutils/dart/additive_expr_2.dart") 41 | 42 | val stringLiteral = psiFile.descendantsOfType().first() 43 | val additiveExpression = psiFile.descendantsOfType().first() 44 | 45 | assertTrue(dartAdditiveExpressionExtractor.isDescendantOf(stringLiteral, additiveExpression)) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/missingtranslations/infrastructure/repository/ExistingTranslationRepositoryIdea.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.missingtranslations.infrastructure.repository 2 | 3 | import com.intellij.openapi.vfs.LocalFileSystem 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.psi.PsiFile 6 | import com.intellij.psi.PsiManager 7 | import com.intellij.openapi.application.ApplicationManager 8 | import de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.entity.ExistingTranslation 9 | import de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.repository.ExistingTranslationRepository 10 | import de.keeyzar.gpthelper.gpthelper.features.psiutils.arb.ArbPsiUtils 11 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 12 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository.TranslationFileRepository 13 | import java.nio.file.Path 14 | 15 | class ExistingTranslationRepositoryIdea( 16 | private val translationFileRepository: TranslationFileRepository, 17 | private val arbPsiUtils: ArbPsiUtils 18 | ) : ExistingTranslationRepository { 19 | override fun getExistingTranslation(reference: PsiElement, baseLanguage: Language, key: String): ExistingTranslation? { 20 | val path = translationFileRepository.getPathToFile(baseLanguage) 21 | val psiFile: PsiFile = getPsiFileFromPath(reference, path) ?: return null 22 | //alright, we can now get the corresponding entry 23 | return arbPsiUtils.getArbEntryFromKeyOnRootObject(psiFile, key)?.let { 24 | return ExistingTranslation( 25 | key = key, 26 | value = it.value, 27 | description = it.description 28 | ) 29 | } 30 | } 31 | 32 | private fun getPsiFileFromPath(element: PsiElement, path: Path): PsiFile? { 33 | val virtualFile = LocalFileSystem.getInstance().findFileByIoFile(path.toFile()) 34 | return virtualFile?.let { 35 | ApplicationManager.getApplication().runReadAction { 36 | PsiManager.getInstance(element.project).findFile(it) 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/psiutils/LiteralInContextFinder.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.psiutils 2 | 3 | import com.intellij.psi.PsiElement 4 | import com.intellij.psi.util.parentOfType 5 | import com.jetbrains.lang.dart.psi.DartCallExpression 6 | import com.jetbrains.lang.dart.psi.DartStatements 7 | import com.jetbrains.lang.dart.psi.DartVarDeclarationList 8 | 9 | /** 10 | * show a psiElement in the context of the file, i.e. show the surrounding Statement 11 | */ 12 | class LiteralInContextFinder( 13 | ) { 14 | /** 15 | * return either this psiElement, or some more context, if possible 16 | */ 17 | fun findContext(psiElement: PsiElement): PsiElement { 18 | //first var declaration list, then call expression, then statement, then psiElement 19 | return ParentFinderChain( 20 | listOf( 21 | ParentFinderDeclarationList(), 22 | ParentFinderCallExpression(), 23 | ParentFinderStatement(), 24 | ) 25 | ).find(psiElement) ?: psiElement 26 | } 27 | 28 | fun interface ParentFinder { 29 | fun find(psiElement: PsiElement): PsiElement? 30 | } 31 | 32 | class ParentFinderChain(private val parentFinders: List) : ParentFinder { 33 | override fun find(psiElement: PsiElement): PsiElement? { 34 | return parentFinders.firstNotNullOfOrNull { it.find(psiElement) } 35 | } 36 | } 37 | 38 | class ParentFinderCallExpression : ParentFinder { 39 | override fun find(psiElement: PsiElement): PsiElement? { 40 | return psiElement.parentOfType() 41 | } 42 | } 43 | 44 | class ParentFinderStatement : ParentFinder { 45 | override fun find(psiElement: PsiElement): PsiElement? { 46 | return psiElement.parentOfType() 47 | } 48 | } 49 | 50 | class ParentFinderDeclarationList : ParentFinder { 51 | override fun find(psiElement: PsiElement): PsiElement? { 52 | //if there is a declaration list, return it, otherwise return the psiElement 53 | return psiElement.parentOfType() 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/kotlin/features/psiutils/DartConstModifierFinderTest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.psiutils 2 | 3 | import com.intellij.psi.util.descendantsOfType 4 | import com.intellij.testFramework.fixtures.BasePlatformTestCase 5 | import com.jetbrains.lang.dart.psi.DartStringLiteralExpression 6 | 7 | class DartConstModifierFinderTest : BasePlatformTestCase() { 8 | private lateinit var sut: DartConstModifierFinder 9 | override fun setUp() { 10 | super.setUp() 11 | sut = DartConstModifierFinder() 12 | myFixture.testDataPath = "src/test/resources/psiutils/dart/const_modifier_finder" 13 | } 14 | 15 | fun testCheckForConstExpressionsInHierarchy_1() { 16 | val psiFile = myFixture.configureByFile("var_declaration_1.dart") 17 | val stringLiteral = psiFile.descendantsOfType().first() 18 | 19 | val constModifier = sut.checkForConstExpressionsInHierarchy(stringLiteral) 20 | 21 | assertEquals("const", constModifier?.text) 22 | } 23 | 24 | fun testCheckForConstExpressionsInHierarchy_2() { 25 | val psiFile = myFixture.configureByFile("list_declaration_1.dart") 26 | val stringLiteral = psiFile.descendantsOfType().first() 27 | 28 | val constModifier = sut.checkForConstExpressionsInHierarchy(stringLiteral) 29 | 30 | assertEquals("const", constModifier?.text) 31 | } 32 | 33 | fun testCheckForConstExpressionsInHierarchy_3() { 34 | val psiFile = myFixture.configureByFile("class_declaration_1.dart") 35 | val stringLiteral = psiFile.descendantsOfType().first() 36 | 37 | val constModifier = sut.checkForConstExpressionsInHierarchy(stringLiteral) 38 | 39 | assertEquals("const", constModifier?.text) 40 | } 41 | 42 | 43 | fun testCheckForConstExpressionsInHierarchy_4() { 44 | val psiFile = myFixture.configureByFile("class_declaration_2.dart") 45 | val stringLiteral = psiFile.descendantsOfType().first() 46 | 47 | val constModifier = sut.checkForConstExpressionsInHierarchy(stringLiteral) 48 | 49 | assertEquals("const", constModifier?.text) 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/infrastructure/search/ArbIndexer.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.infrastructure.search 2 | 3 | import com.google.gson.Gson 4 | import java.text.Normalizer 5 | 6 | /** 7 | * Builds an in-memory index from the raw ARB JSON content. 8 | * Does not perform any IO; accepts the JSON content as a String. 9 | */ 10 | class ArbIndexer { 11 | private val gson = Gson() 12 | 13 | fun buildIndex(arbJsonContent: String): List { 14 | if (arbJsonContent.isBlank()) return emptyList() 15 | 16 | val parsed = try { 17 | gson.fromJson(arbJsonContent, Map::class.java) as? Map ?: return emptyList() 18 | } catch (e: Exception) { 19 | return emptyList() 20 | } 21 | 22 | return parsed.entries 23 | .asSequence() 24 | // filter out metadata entries that start with @ 25 | .filter { (k, _) -> !k.startsWith("@") } 26 | .mapNotNull { (k, v) -> 27 | val value = v as? String ?: return@mapNotNull null 28 | val normalized = normalize(value) 29 | val trigrams = generateTrigrams(normalized) 30 | ArbIndexEntry( 31 | key = k, 32 | value = value, 33 | normalizedValue = normalized, 34 | trigrams = trigrams 35 | ) 36 | } 37 | .toList() 38 | } 39 | 40 | private fun normalize(input: String): String { 41 | // lowercase and remove diacritics 42 | val lower = input.lowercase() 43 | val normalized = Normalizer.normalize(lower, Normalizer.Form.NFD) 44 | .replace("\\p{M}+".toRegex(), "") 45 | return normalized 46 | } 47 | 48 | private fun generateTrigrams(s: String): Set { 49 | val cleaned = s.replace("\\s+".toRegex(), " ").trim() 50 | if (cleaned.length <= 3) return setOf(cleaned) 51 | 52 | val trigrams = mutableSetOf() 53 | for (i in 0..cleaned.length - 3) { 54 | trigrams.add(cleaned.substring(i, i + 3)) 55 | } 56 | return trigrams 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/flutter_intl/infrastructure/service/ArbFilesService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.flutter_intl.infrastructure.service 2 | 3 | import com.intellij.openapi.project.Project 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 5 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.exceptions.NoTranslationFilesException 6 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.parser.ArbFilenameParser 7 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository.TranslationFileRepository 8 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository.UserSettingsRepository 9 | import java.nio.file.Path 10 | 11 | /** 12 | * helper class providing a lot of functions to work with arb files 13 | * TODO can be a facade for multiple minor services, which are not exposed to the outside 14 | */ 15 | class ArbFilesService( 16 | private val arbFilenameParser: ArbFilenameParser, 17 | private val translationFileRepository: TranslationFileRepository, 18 | private val project: Project, 19 | private val userSettingsRepository: UserSettingsRepository, 20 | ) { 21 | 22 | fun findAvailableLanguages(): List { 23 | val pathsToTranslationFiles = translationFileRepository.getPathsToTranslationFiles() 24 | if (pathsToTranslationFiles.isEmpty()) { 25 | throw NoTranslationFilesException("There are no translation files in your project, you might want to create some") 26 | } 27 | return pathsToTranslationFiles 28 | .map { arbFilenameParser.getLanguageFromPath(it) } 29 | } 30 | 31 | /** 32 | * either you provide a path which we should get the language from, or it'll be returned from the userSettings configured basePath 33 | */ 34 | fun getBaseLanguage(path: Path?): Language { 35 | val definitivePath = when (path) { 36 | null -> { 37 | val relativePath = userSettingsRepository.getSettings().templateArbFile 38 | Path.of("${project.basePath}/$relativePath") 39 | } 40 | else -> path 41 | } 42 | return arbFilenameParser.getLanguageFromPath(definitivePath) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/presentation/widgets/RetryFailedTranslationsDialog.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.presentation.widgets 2 | 3 | import com.intellij.openapi.ui.DialogWrapper 4 | import com.intellij.ui.components.JBCheckBox 5 | import com.intellij.ui.components.JBScrollPane 6 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.UserTranslationRequest 7 | import java.awt.BorderLayout 8 | import java.awt.Dimension 9 | import javax.swing.BoxLayout 10 | import javax.swing.JComponent 11 | import javax.swing.JPanel 12 | import javax.swing.JScrollPane 13 | 14 | class RetryFailedTranslationsDialog( 15 | private val failedRequests: List 16 | ) : DialogWrapper(true) { 17 | 18 | private val models: List 19 | 20 | init { 21 | title = "Retry Failed Translations" 22 | setOKButtonText("Retry Selected") 23 | setCancelButtonText("Skip") 24 | models = failedRequests.map { FailedRequestModel(it) } 25 | super.init() 26 | } 27 | 28 | override fun createCenterPanel(): JComponent { 29 | val panel = JPanel(BorderLayout()) 30 | panel.preferredSize = Dimension(600, 400) 31 | 32 | val checkboxPanel = JPanel().apply { 33 | layout = BoxLayout(this, BoxLayout.Y_AXIS) 34 | } 35 | 36 | models.forEach { model -> 37 | val text = "Key: ${model.request.baseTranslation.entry.desiredKey} - Value: ${model.request.baseTranslation.entry.desiredValue}" 38 | val cb = JBCheckBox(text, true) 39 | cb.addActionListener { 40 | model.checked = cb.isSelected 41 | } 42 | checkboxPanel.add(cb) 43 | } 44 | 45 | panel.add(JBScrollPane(checkboxPanel).apply { 46 | verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED 47 | }, BorderLayout.CENTER) 48 | 49 | return panel 50 | } 51 | 52 | fun getRequestsToRetry(): List { 53 | return models.filter { it.checked }.map { it.request } 54 | } 55 | 56 | internal data class FailedRequestModel( 57 | val request: UserTranslationRequest, 58 | var checked: Boolean = true 59 | ) 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/domain/service/IdeaGatherBestGuessContext.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.service 2 | 3 | import com.intellij.psi.PsiElement 4 | import com.intellij.psi.PsiFile 5 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity.FileBestGuessContext 6 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity.SingleLiteral 7 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.infrastructure.service.PsiElementIdReferenceProvider 8 | import de.keeyzar.gpthelper.gpthelper.features.psiutils.PsiElementIdGenerator 9 | import de.keeyzar.gpthelper.gpthelper.features.shared.domain.exception.ProgrammerException 10 | import de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service.ContextProvider 11 | import java.util.* 12 | 13 | class IdeaGatherBestGuessContext( 14 | private val contextProvider: ContextProvider, 15 | private val psiElementIdGenerator: PsiElementIdGenerator, 16 | private val psiElementReferenceContainer: PsiElementIdReferenceProvider, 17 | ) : GatherBestGuessContext { 18 | 19 | override fun fromMultipleFiles(processUUID: UUID, files: List): FileBestGuessContext? { 20 | TODO("Not yet implemented") 21 | } 22 | 23 | override fun fromPsiElements(processUUID: UUID, elements: List): FileBestGuessContext? { 24 | if (elements.isEmpty()) { 25 | return null 26 | } 27 | val context = contextProvider.getAutoLocalizeContext(processUUID) 28 | ?: throw ProgrammerException("The programmer forgot to set the context, or there was some other strange mystery. Anyways, we did not find the context for the process with id: '$processUUID'") 29 | 30 | val stringLiterals = elements 31 | .fold(ArrayList()) { stringLiterals, psiElement -> 32 | val id = psiElementIdGenerator.createIdFromPsiElement(psiElement) 33 | psiElementReferenceContainer.putElement(id, psiElement) 34 | stringLiterals += SingleLiteral(id, psiElement.text) 35 | stringLiterals 36 | } 37 | 38 | 39 | return FileBestGuessContext( 40 | filename = context.baseFile.name, 41 | literals = stringLiterals 42 | ) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/presentation/service/IdeaWaitingIndicatorService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.presentation.service 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.application.ModalityState 5 | import com.intellij.openapi.progress.ProgressIndicator 6 | import com.intellij.openapi.progress.ProgressManager 7 | import com.intellij.openapi.progress.Task 8 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.service.WaitingIndicatorService 9 | import de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service.ContextProvider 10 | import java.util.* 11 | 12 | class IdeaWaitingIndicatorService( 13 | private val contextProvider: ContextProvider, 14 | ) 15 | : WaitingIndicatorService { 16 | private var progressIndicator: ProgressIndicator? = null 17 | 18 | override fun startWaiting(uuid: UUID, title: String, description: String) { 19 | val project = contextProvider.getAutoLocalizeContext(uuid)!!.project 20 | val task = object : Task.Modal(project, title, true) { 21 | override fun run(indicator: ProgressIndicator) { 22 | progressIndicator = indicator 23 | indicator.isIndeterminate = true 24 | indicator.text = description 25 | while (!indicator.isCanceled) { 26 | // Do nothing and keep waiting 27 | Thread.sleep(100) 28 | } 29 | } 30 | } 31 | 32 | // Ensure we run on the main event dispatch thread 33 | ApplicationManager.getApplication().invokeLater { 34 | ProgressManager.getInstance().run(task) 35 | } 36 | } 37 | 38 | override fun stopWaiting() { 39 | // Ensure we run on the main event dispatch thread 40 | ApplicationManager.getApplication().invokeLater({ 41 | progressIndicator?.cancel() 42 | progressIndicator = null 43 | }, ModalityState.any()) //any, because the modal is blocking the EDT 44 | } 45 | 46 | override fun updateProgress(uuid: UUID, progressText: String) { 47 | ApplicationManager.getApplication().invokeLater({ 48 | progressIndicator?.text = progressText 49 | }, ModalityState.any()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/kotlin/features/shared/infrastructure/utils/JsonUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.shared.infrastructure.utils 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import org.junit.Test 5 | import org.junit.jupiter.api.Assertions.* 6 | 7 | class JsonUtilsTest { 8 | private val sut = JsonUtils(ObjectMapper()) 9 | 10 | @Test 11 | fun `test reorderJson when keys are in the same order`() { 12 | val base = "{\"key1\":\"value1\",\"key2\":\"value2\"}" 13 | val scrambled = "{\"key1\":\"scrambledValue1\",\"key2\":\"scrambledValue2\"}" 14 | val expected = "{\"key1\":\"scrambledValue1\",\"key2\":\"scrambledValue2\"}" 15 | val result = sut.reorderJson(base, scrambled) 16 | assertEquals(expected, result) 17 | } 18 | 19 | @Test 20 | fun `test reorderJson when keys are in a different order`() { 21 | val base = "{\"key1\":\"value1\",\"key2\":\"value2\"}" 22 | val scrambled = "{\"key2\":\"scrambledValue2\",\"key1\":\"scrambledValue1\"}" 23 | val expected = "{\"key1\":\"scrambledValue1\",\"key2\":\"scrambledValue2\"}" 24 | val result = sut.reorderJson(base, scrambled) 25 | assertEquals(expected, result) 26 | } 27 | 28 | @Test 29 | fun `test reorderJson when some keys are missing in scrambled JSON`() { 30 | val base = "{\"key1\":\"value1\",\"key2\":\"value2\",\"key3\":\"value3\"}" 31 | val scrambled = "{\"key1\":\"scrambledValue1\",\"key3\":\"scrambledValue3\"}" 32 | val expected = "{\"key1\":\"scrambledValue1\",\"key3\":\"scrambledValue3\"}" 33 | val result = sut.reorderJson(base, scrambled) 34 | assertEquals(expected, result) 35 | } 36 | 37 | @Test 38 | fun `test reorderJson when base JSON is empty`() { 39 | val base = "{}" 40 | val scrambled = "{\"key1\":\"scrambledValue1\",\"key2\":\"scrambledValue2\"}" 41 | val expected = "{}" 42 | val result = sut.reorderJson(base, scrambled) 43 | assertEquals(expected, result) 44 | } 45 | 46 | @Test 47 | fun `test reorderJson when scrambled JSON is empty`() { 48 | val base = "{\"key1\":\"value1\",\"key2\":\"value2\"}" 49 | val scrambled = "{}" 50 | val expected = "{}" 51 | val result = sut.reorderJson(base, scrambled) 52 | assertEquals(expected, result) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/missingtranslations/infrastructure/service/MissingTranslationCollectionServiceIdea.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.missingtranslations.infrastructure.service 2 | 3 | import com.intellij.psi.PsiElement 4 | import de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.entity.MissingTranslation 5 | import de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.entity.MissingTranslationContext 6 | import de.keeyzar.gpthelper.gpthelper.features.missingtranslations.domain.service.MissingTranslationCollectionService 7 | import de.keeyzar.gpthelper.gpthelper.features.psiutils.arb.ArbPsiUtils 8 | import de.keeyzar.gpthelper.gpthelper.features.psiutils.arb.StringArrayContent 9 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Language 10 | 11 | class MissingTranslationCollectionServiceIdea( 12 | private val arbPsiUtils: ArbPsiUtils, 13 | ) : MissingTranslationCollectionService { 14 | 15 | override fun collectMissingTranslations(missingTranslationContext: MissingTranslationContext): MissingTranslationContext { 16 | //essentially, we're collecting de -> "greeting" and pl -> "greeting" and fr -> "greeting" 17 | //then we invert it to be "greeting" -> de, pl, fr 18 | val allStringArraysFromRoot: List = arbPsiUtils.getAllStringArraysFromRoot(missingTranslationContext.reference) 19 | 20 | val collectToMap: Map> = allStringArraysFromRoot 21 | .flatMap { stringArrayContent -> stringArrayContent.values.map { arbEntryKey -> Pair(arbEntryKey, stringArrayContent.key) } } 22 | .groupBy({ it.first }, { it.second }) 23 | 24 | val missingTranslations: List = collectToMap 25 | .map { outer -> MissingTranslation(outer.key, outer.value.map { getLanguageFromString(it) }) } 26 | 27 | return MissingTranslationContext(uuid = missingTranslationContext.uuid, missingTranslations, reference = missingTranslationContext.reference) 28 | } 29 | private fun getLanguageFromString(language: String): Language { 30 | //either it's de or de_DE (or en_US or en) 31 | val split = language.split("_") 32 | return if (split.size == 2) { 33 | Language(split[0], split[1]) 34 | } else { 35 | Language(split[0], null) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/client/DispatcherConfiguration.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.client 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository.TranslationCredentialsServiceRepository 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.repository.UserSettingsRepository 5 | import groovy.util.logging.Slf4j 6 | import kotlinx.coroutines.ExecutorCoroutineDispatcher 7 | import kotlinx.coroutines.asCoroutineDispatcher 8 | import java.util.concurrent.Executors 9 | 10 | @Slf4j 11 | class DispatcherConfiguration( 12 | private val userSettingsRepository: UserSettingsRepository, 13 | private val translationCredentialsServiceRepository: TranslationCredentialsServiceRepository, 14 | ) { 15 | private var dispatcher: ExecutorCoroutineDispatcher? = null 16 | 17 | //default level of parallelism - because openAI lets you only make 3 calls in parallel at account creation for some days 18 | private var levelOfParallelism: Int = 3 19 | private var dirty = false 20 | 21 | fun getDispatcher(): ExecutorCoroutineDispatcher { 22 | checkIfParallelismChanged() 23 | closeDispatcherIfRequired() 24 | var tmpDispatcher = this.dispatcher 25 | if (tmpDispatcher == null) { 26 | tmpDispatcher = Executors.newFixedThreadPool(levelOfParallelism).asCoroutineDispatcher(); 27 | this.dispatcher = tmpDispatcher 28 | return tmpDispatcher 29 | } 30 | return tmpDispatcher; 31 | } 32 | 33 | fun getLevelOfParallelism(): Int { 34 | return levelOfParallelism 35 | } 36 | 37 | private fun closeDispatcherIfRequired() { 38 | if (dirty) { 39 | dispatcher?.close() 40 | dispatcher = null 41 | dirty = false 42 | } 43 | } 44 | 45 | private fun checkIfParallelismChanged() { 46 | val key = translationCredentialsServiceRepository.getKey() 47 | val newParallelism = if (key.isNullOrBlank()) { 48 | 3 49 | } else { 50 | userSettingsRepository.getSettings().parallelism 51 | } 52 | 53 | if (newParallelism != levelOfParallelism) { 54 | println("parallelism changed from $levelOfParallelism to $newParallelism, creating new dispatcher") 55 | levelOfParallelism = newParallelism 56 | dirty = true 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/kotlin/features/translations/infrastructure/service/JsonChunkMergerTest.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import org.assertj.core.api.Assertions.assertThat 5 | import org.junit.Test 6 | 7 | class JsonChunkMergerTest { 8 | private val objectMapper = ObjectMapper() 9 | private val merger = JsonChunkMerger(objectMapper) 10 | 11 | @Test 12 | fun `mergeJsonChunks should merge list of JSON chunks into single JSON object`() { 13 | val chunk1 = """{"prop1":1,"prop2":2,"prop3":3}""" 14 | val chunk2 = """{"prop4":4,"prop5":5}""" 15 | val expectedMergedJson = """{"prop1":1,"prop2":2,"prop3":3,"prop4":4,"prop5":5}""" 16 | 17 | val jsonChunks = listOf(chunk1, chunk2) 18 | val actualMergedJson = merger.mergeChunks(jsonChunks).replace("\\s".toRegex(), "") 19 | 20 | assertThat(actualMergedJson).isEqualTo(expectedMergedJson) 21 | } 22 | 23 | @Test 24 | fun `mergeJsonChunks should merge list of JSON properties chunks into single JSON object`() { 25 | val chunk1 = """"prop1":1,"prop2":2,"prop3":3""" 26 | val chunk2 = """"prop4":4,"prop5":5""" 27 | val expectedMergedJson = """{"prop1":1,"prop2":2,"prop3":3,"prop4":4,"prop5":5}""" 28 | 29 | val jsonChunks = listOf(chunk1, chunk2) 30 | val actualMergedJson = merger.mergeChunks(jsonChunks).replace("\\s".toRegex(), "") 31 | 32 | assertThat(actualMergedJson).isEqualTo(expectedMergedJson) 33 | } 34 | 35 | @Test 36 | fun `mergeJsonChunks should return empty object if list of JSON chunks is empty`() { 37 | val expectedMergedJson = "{}" 38 | 39 | val jsonChunks = emptyList() 40 | val actualMergedJson = merger.mergeChunks(jsonChunks).replace("\\s".toRegex(), "") 41 | 42 | assertThat(actualMergedJson).isEqualTo(expectedMergedJson) 43 | } 44 | 45 | @Test 46 | fun `mergeJsonChunks should merge list of JSON chunks with duplicate keys`() { 47 | val chunk1 = """{"prop1":1,"prop2":2,"prop3":3}""" 48 | val chunk2 = """{"prop3":4,"prop5":5}""" 49 | val expectedMergedJson = """{"prop1":1,"prop2":2,"prop3":4,"prop5":5}""" 50 | 51 | val jsonChunks = listOf(chunk1, chunk2) 52 | val actualMergedJson = merger.mergeChunks(jsonChunks).replace("\\s".toRegex(), "") 53 | 54 | assertThat(actualMergedJson).isEqualTo(expectedMergedJson) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/autofilefixer/infrastructure/service/IdeaPreFilterRequestBuilder.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.autofilefixer.infrastructure.service 2 | 3 | import com.intellij.openapi.application.ReadAction 4 | import com.intellij.psi.PsiElement 5 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity.PreFilterLiteral 6 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.entity.PreFilterRequest 7 | import de.keeyzar.gpthelper.gpthelper.features.autofilefixer.domain.service.PreFilterRequestBuilder 8 | import de.keeyzar.gpthelper.gpthelper.features.psiutils.LiteralInContextFinder 9 | import de.keeyzar.gpthelper.gpthelper.features.psiutils.SmartPsiElementWrapper 10 | import java.util.concurrent.atomic.AtomicInteger 11 | 12 | class IdeaPreFilterRequestBuilder( 13 | private val literalInContextFinder: LiteralInContextFinder 14 | ) : PreFilterRequestBuilder { 15 | 16 | companion object { 17 | private val idCounter = AtomicInteger(0) 18 | } 19 | 20 | override fun buildRequest(elements: Map, Boolean>): PreFilterRequest { 21 | // Reset counter for each request to keep IDs simple 22 | idCounter.set(0) 23 | 24 | // Resolve all pointers and filter out invalid elements 25 | val literals = elements.mapNotNull { (wrapper, _) -> 26 | // SmartPointer.getElement() internally calls isValid() which requires read access 27 | val psiElement = ReadAction.compute { 28 | wrapper.element 29 | } 30 | 31 | psiElement?.let { 32 | val id = idCounter.incrementAndGet().toString() 33 | val literalText = ReadAction.compute { 34 | it.text.removeSurrounding("\"").removeSurrounding("'") 35 | } 36 | val contextPsi = ReadAction.compute { 37 | literalInContextFinder.findContext(it) 38 | } 39 | val context = ReadAction.compute { 40 | contextPsi.text 41 | } 42 | 43 | PreFilterLiteral( 44 | id = id, 45 | literalText = literalText, 46 | context = context, 47 | psiElement = it 48 | ) 49 | } 50 | } 51 | 52 | return PreFilterRequest(literals) 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /src/main/kotlin/de/keeyzar/gpthelper/gpthelper/features/translations/infrastructure/service/ArbContentModificationService.kt: -------------------------------------------------------------------------------- 1 | package de.keeyzar.gpthelper.gpthelper.features.translations.infrastructure.service 2 | 3 | import de.keeyzar.gpthelper.gpthelper.features.shared.infrastructure.utils.JsonUtils 4 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.FileToTranslate 5 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.entity.Translation 6 | import de.keeyzar.gpthelper.gpthelper.features.translations.domain.service.ContentModificationService 7 | 8 | class ArbContentModificationService(private val jsonUtils: JsonUtils) : ContentModificationService { 9 | override fun appendTranslation(fileToTranslate: FileToTranslate, translation: Translation): FileToTranslate { 10 | val newContentToAppend = jsonUtils.entryToJsonStringWithoutSurroundingBrackets(translation.entry) 11 | val newFileContent = appendNewContent(fileToTranslate.content, newContentToAppend) 12 | return fileToTranslate.copy(content = newFileContent); 13 | } 14 | 15 | override fun replaceWithNewTranslation(fileToTranslate: FileToTranslate, translation: Translation): FileToTranslate { 16 | val newFileContent = jsonUtils.replaceKeys(fileToTranslate.content, translation.entry) 17 | return fileToTranslate.copy(content = newFileContent); 18 | } 19 | 20 | private fun appendNewContent(fileContent: String, newContent: String): String { 21 | var newFile = findAndReplaceWithExistingEntry(fileContent, newContent) 22 | if (newFile == null) { 23 | newFile = findAndReplaceWithoutEntry(fileContent, newContent) 24 | } 25 | return newFile 26 | } 27 | 28 | 29 | private fun findAndReplaceWithoutEntry(fileContent: String, newContent: String): String { 30 | val lastIndex = fileContent.lastIndexOf("}") 31 | return if (lastIndex != -1) { 32 | fileContent.substring(0, lastIndex) + " \n " + newContent + " \n}" 33 | } else { 34 | " {\n $newContent \n}" 35 | } 36 | } 37 | 38 | private fun findAndReplaceWithExistingEntry(fileContent: String, newContent: String): String? { 39 | val hasEntries = jsonUtils.hasAnyEntry(fileContent) 40 | if (!hasEntries) return null; 41 | 42 | val lastIndex = fileContent.lastIndexOf("}") 43 | if (lastIndex != -1) { 44 | return fileContent.substring(0, lastIndex) + ",\n " + newContent + "\n}" 45 | 46 | } 47 | return null 48 | } 49 | 50 | } 51 | --------------------------------------------------------------------------------