├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── codecov.yml └── workflows │ ├── basic.yml │ ├── dependency-analysis.yml │ ├── lint-pr-title.yml │ └── publish.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── resources ├── autocomplete.gif ├── download.gif ├── progress.gif ├── settings.png └── upload.gif ├── settings.gradle └── src ├── main ├── java │ └── com │ │ └── crowdin │ │ ├── Constants.java │ │ ├── action │ │ ├── ActionContext.java │ │ ├── BackgroundAction.java │ │ ├── BundleSettingsAction.java │ │ ├── DownloadAction.java │ │ ├── DownloadSourceFromContextAction.java │ │ ├── DownloadSourcesAction.java │ │ ├── DownloadTranslationFromContextAction.java │ │ ├── SettingsAction.java │ │ ├── UploadAction.java │ │ ├── UploadFromContextAction.java │ │ ├── UploadTranslationsAction.java │ │ └── UploadTranslationsFromContextAction.java │ │ ├── activity │ │ └── CrowdinStartupActivity.java │ │ ├── client │ │ ├── BranchInfo.java │ │ ├── Crowdin.java │ │ ├── CrowdinClient.java │ │ ├── RequestBuilder.java │ │ └── config │ │ │ ├── CrowdinConfig.java │ │ │ ├── CrowdinFileProvider.java │ │ │ ├── CrowdinPropertiesLoader.java │ │ │ └── FileBean.java │ │ ├── completion │ │ └── StringsCompletionContributor.java │ │ ├── event │ │ └── FileChangeListener.java │ │ ├── logic │ │ ├── BranchLogic.java │ │ ├── ContextLogic.java │ │ ├── DownloadBundleLogic.java │ │ ├── DownloadTranslationsLogic.java │ │ └── SourceLogic.java │ │ ├── service │ │ ├── CrowdinProjectCacheProvider.java │ │ └── ProjectService.java │ │ ├── settings │ │ ├── CrowdinSettingsConfigurable.java │ │ ├── CrowdingSettingsState.java │ │ └── SettingsPanel.java │ │ ├── ui │ │ ├── dialog │ │ │ ├── ConfirmActionDialog.java │ │ │ ├── ConfirmActionPanel.form │ │ │ └── ConfirmActionPanel.java │ │ ├── panel │ │ │ ├── ContentTab.java │ │ │ ├── CrowdinPanelWindowFactory.java │ │ │ ├── download │ │ │ │ ├── DownloadWindow.java │ │ │ │ └── action │ │ │ │ │ ├── CollapseAction.java │ │ │ │ │ ├── ExpandAction.java │ │ │ │ │ └── RefreshAction.java │ │ │ ├── progress │ │ │ │ ├── TranslationProgressWindow.form │ │ │ │ ├── TranslationProgressWindow.java │ │ │ │ ├── TreeCellLanguage.form │ │ │ │ ├── TreeCellLanguage.java │ │ │ │ └── action │ │ │ │ │ ├── GroupProgressByFiles.java │ │ │ │ │ └── RefreshTranslationProgressAction.java │ │ │ └── upload │ │ │ │ ├── UploadWindow.java │ │ │ │ └── action │ │ │ │ ├── CollapseAction.java │ │ │ │ ├── ExpandAction.java │ │ │ │ └── RefreshAction.java │ │ └── tree │ │ │ ├── CellData.java │ │ │ ├── CellRenderer.java │ │ │ └── FileTree.java │ │ └── util │ │ ├── CrowdinFileUtil.java │ │ ├── FileUtil.java │ │ ├── GitUtil.java │ │ ├── LanguageMapping.java │ │ ├── NotificationUtil.java │ │ ├── PlaceholderUtil.java │ │ ├── RetryUtil.java │ │ ├── StringUtils.java │ │ └── Util.java └── resources │ ├── META-INF │ ├── plugin.xml │ ├── pluginIcon.svg │ └── pluginIcon_dark.svg │ ├── icons │ ├── approved.svg │ ├── download-sources.svg │ ├── download-sources_dark.svg │ ├── download.svg │ ├── download_dark.svg │ ├── folder.svg │ ├── folder_dark.svg │ ├── icon.svg │ ├── icon80x80.png │ ├── icon_dark.svg │ ├── translated.svg │ ├── upload-sources.svg │ ├── upload-sources_dark.svg │ ├── upload.svg │ └── upload_dark.svg │ └── messages │ └── messages.properties └── test ├── java └── com │ └── crowdin │ ├── api │ └── model │ │ ├── BranchBuilder.java │ │ ├── DirectoryBuilder.java │ │ ├── FileBuilder.java │ │ └── LanguageBuilder.java │ ├── client │ ├── CrowdinProjectCacheProviderTest.java │ ├── FileBeanBuilder.java │ ├── MockCrowdin.java │ └── config │ │ └── CrowdinPropertiesLoaderTest.java │ ├── logic │ ├── BranchLogicTest.java │ └── ContextLogicTest.java │ ├── ui │ └── FileTreeTest.java │ └── util │ ├── CrowdinFileUtilTest.java │ ├── FileUtilTest.java │ ├── GitUtilTest.java │ ├── LanguageMappingTest.java │ ├── PlaceholderUtilTest.java │ ├── RetryUtilTest.java │ └── UtilTest.java ├── resources ├── file1.yml └── file2.yml └── testData └── values ├── second └── values │ └── strings.xml ├── strings.xml └── strings2.xml /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Configuration file: '...' 16 | 2. Localization files structure: '...' 17 | 3. Click on '....' 18 | 4. See error 19 | 20 | **Environment** 21 | 1. Android Studio version [e.g., 3.6.3] 22 | 2. OS version [e.g., Windows 10] 23 | 24 | **Expected behavior** 25 | A clear and concise description of what you expected to happen. 26 | 27 | **Screenshots** 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | **Additional context** 31 | 32 | Run the plugin in Debug mode and provide an extended log. 33 | To enable Debug mode, add `debug=true` to the *crowdin.yml* file. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | github_checks: 2 | annotations: false 3 | -------------------------------------------------------------------------------- /.github/workflows/basic.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - 'master' 7 | push: 8 | branches: 9 | - '*' 10 | paths-ignore: 11 | - 'README.md' 12 | - 'LICENSE' 13 | - 'CODE_OF_CONDUCT.md' 14 | - 'CONTRIBUTING.md' 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - uses: actions/setup-java@v4 23 | with: 24 | distribution: temurin 25 | java-version: 17 26 | cache: 'gradle' 27 | 28 | - name: Execute Gradle 29 | run: ./gradlew build buildPlugin verifyPlugin 30 | 31 | - name: Run Tests 32 | run: ./gradlew jacocoTestReport 33 | 34 | - name: Upload coverage to Codecov 35 | uses: codecov/codecov-action@v4 36 | with: 37 | token: ${{ secrets.CODECOV_TOKEN }} 38 | 39 | - uses: actions/upload-artifact@v4 40 | with: 41 | name: build 42 | path: build/distributions/Crowdin-*.zip 43 | -------------------------------------------------------------------------------- /.github/workflows/dependency-analysis.yml: -------------------------------------------------------------------------------- 1 | name: Dependency Analysis 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | schedule: 8 | - cron: '0 0 * * MON' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | dependency-analysis: 13 | uses: crowdin/.github/.github/workflows/dependency-analysis.yml@main 14 | secrets: 15 | FOSSA_API_KEY: ${{ secrets.FOSSA_API_KEY }} 16 | -------------------------------------------------------------------------------- /.github/workflows/lint-pr-title.yml: -------------------------------------------------------------------------------- 1 | name: lint-pr-title 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - reopened 8 | - edited 9 | - synchronize 10 | 11 | jobs: 12 | main: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: amannn/action-semantic-pull-request@v5 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Package 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | jobs: 8 | package: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - uses: actions/setup-java@v4 14 | with: 15 | distribution: temurin 16 | java-version: 17 17 | cache: 'gradle' 18 | 19 | - name: Execute Gradle 20 | run: ./gradlew build buildPlugin verifyPlugin 21 | 22 | - name: Upload asset 23 | uses: softprops/action-gh-release@v2 24 | with: 25 | files: build/distributions/Crowdin-*.zip 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | *.class 4 | .gradle 5 | 6 | /target 7 | /build 8 | /out 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at support@crowdin.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | :tada: First off, thanks for taking the time to contribute! :tada: 4 | 5 | The following is a set of guidelines for contributing to Crowdin Android Studio Plugin. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | This project and everyone participating in it are governed by the [Code of Conduct](/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. 8 | 9 | ## How can I contribute? 10 | 11 | ### Star this repo 12 | 13 | It's quick and goes a long way! :stars: 14 | 15 | ### Reporting Bugs 16 | 17 | This section guides you through submitting a bug report for Crowdin Android Studio Plugin. Following these guidelines helps maintainers, and the community understand your report :pencil:, reproduce the behavior :computer:, and find related reports :mag_right:. 18 | 19 | When you are creating a bug report, please include as many details as possible. Fill out the required issue template, the information it asks for helps us resolve issues faster. 20 | 21 | #### How Do I Submit a Bug Report? 22 | 23 | Bugs are tracked as [GitHub issues](https://github.com/crowdin/android-studio-plugin/issues/). 24 | 25 | Explain the problem and include additional details to help reproduce the problem: 26 | 27 | * **Use a clear and descriptive title** for the issue to identify the problem. 28 | * **Describe the exact steps which reproduce the problem** in as many details as possible. Don't just say what you did, but explain how you did it. 29 | * **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. 30 | * **Explain which behavior you expected to see instead and why.** 31 | 32 | Include details about your configuration and environment: 33 | 34 | * Which operating system are you using? 35 | * Are you using some proxies or firewalls in your network? 36 | 37 | ### Suggesting Enhancements 38 | 39 | This section guides you through submitting an enhancement suggestion for Crowdin Android Studio Plugin, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:. 40 | 41 | When you are creating an enhancement suggestion, please include as many details as possible. Fill in feature request, including the steps that you imagine you would take if the feature you're requesting existed. 42 | 43 | #### How Do I Submit an Enhancement Suggestion? 44 | 45 | Enhancement suggestions are tracked as [GitHub issues](https://github.com/crowdin/android-studio-plugin/issues/). 46 | 47 | Create an issue on that repository and provide the following information: 48 | 49 | * **Use a clear and descriptive title** for the issue to identify the suggestion. 50 | * **Provide a step-by-step description of the suggested enhancement** in as many details as possible. 51 | * **Describe the current behavior** and **explain which behavior you expected to see instead** and why. 52 | * **Explain why this enhancement would be useful** to most Android Studio Plugin users. 53 | 54 | ### Your First Code Contribution 55 | 56 | Unsure where to begin contributing to Crowdin Android Studio Plugin? You can start by looking through these `good-first-issue` and `help-wanted` issues: 57 | 58 | * [Good first issue](https://github.com/crowdin/android-studio-plugin/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) - issues which should only require a small amount of code, and a test or two. 59 | * [Help wanted](https://github.com/crowdin/android-studio-plugin/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) - issues which should be a bit more involved than `Good first issue` issues. 60 | 61 | #### Pull Request Checklist 62 | 63 | Before sending your pull requests, make sure you followed the list below: 64 | 65 | - Read these guidelines. 66 | - Read [Code of Conduct](/CODE_OF_CONDUCT.md). 67 | - Ensure that your code adheres to standard conventions, as used in the rest of the project. 68 | - Ensure that there are unit tests for your code. 69 | - Run unit tests. 70 | 71 | > **Note** 72 | > This project uses the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification for commit messages and PR titles. 73 | 74 | #### Philosophy of code contribution 75 | 76 | - Include unit tests when you contribute new features, as they help to a) prove that your code works correctly, and b) guard against future breaking changes to lower the maintenance cost. 77 | - Bug fixes also generally require unit tests, because the presence of bugs usually indicates insufficient test coverage. 78 | 79 | #### How to run and test plugin? 80 | 81 | **Building:** 82 | 83 | ```console 84 | ./gradlew buildPlugin 85 | ``` 86 | 87 | The build will be located in `build/distringutions/` folder. 88 | 89 | **Verifying:** 90 | 91 | ```console 92 | ./gradlew verifyPlugin 93 | ``` 94 | 95 | **Running in IDE:** 96 | 97 | ```console 98 | ./gradlew runIde 99 | ``` 100 | 101 | This command will start a test IDE ready for use with plugin installed. 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) crowdin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.jetbrains.intellij' version '1.17.0' 4 | id 'jacoco' 5 | } 6 | 7 | group 'com.crowdin.crowdin-idea' 8 | version '2.2.0' 9 | 10 | sourceCompatibility = '17' 11 | 12 | repositories { 13 | mavenCentral() 14 | maven { url "https://jitpack.io" } 15 | } 16 | 17 | dependencies { 18 | 19 | implementation 'net.lingala.zip4j:zip4j:2.11.3' 20 | implementation 'com.github.crowdin:crowdin-api-client-java:1.19.5' 21 | implementation 'commons-io:commons-io:2.15.1' 22 | implementation 'org.yaml:snakeyaml:2.2' 23 | 24 | testImplementation 'junit:junit:4.13.1' 25 | testImplementation 'org.junit.jupiter:junit-jupiter:5.5.2' 26 | testImplementation 'org.hamcrest:hamcrest:2.2' 27 | testImplementation 'org.mockito:mockito-core:4.0.0' 28 | testImplementation 'org.mockito:mockito-junit-jupiter:4.0.0' 29 | testImplementation 'org.mockito:mockito-inline:4.0.0' 30 | } 31 | 32 | test { 33 | useJUnitPlatform() 34 | } 35 | 36 | intellij { 37 | version = '2022.3.3' 38 | plugins = ['Git4Idea'] 39 | updateSinceUntilBuild = false 40 | } 41 | patchPluginXml { 42 | changeNotes = """ 43 | - Add escape_special_characters option support""" 44 | } 45 | 46 | wrapper { 47 | gradleVersion = '8.5' 48 | } 49 | 50 | jacoco { 51 | toolVersion = '0.8.10' 52 | reportsDirectory = layout.buildDirectory.dir('reports') 53 | } 54 | 55 | //https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin-faq.html#jacoco-reports-0-coverage 56 | test { 57 | jacoco { 58 | includeNoLocationClasses = true 59 | excludes = ["jdk.internal.*"] 60 | } 61 | } 62 | 63 | jacocoTestReport { 64 | classDirectories.setFrom(instrumentCode) 65 | 66 | reports { 67 | xml.required = true 68 | csv.required = false 69 | xml.outputLocation = layout.buildDirectory.file('coverage.xml') 70 | } 71 | 72 | getExecutionData().setFrom(layout.buildDirectory.file('jacoco/test.exec')) 73 | } 74 | 75 | jacocoTestCoverageVerification { 76 | classDirectories.setFrom(instrumentCode) 77 | } 78 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crowdin/android-studio-plugin/855a24eb0c5c00d0e0be465af8438ce88717c692/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /resources/autocomplete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crowdin/android-studio-plugin/855a24eb0c5c00d0e0be465af8438ce88717c692/resources/autocomplete.gif -------------------------------------------------------------------------------- /resources/download.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crowdin/android-studio-plugin/855a24eb0c5c00d0e0be465af8438ce88717c692/resources/download.gif -------------------------------------------------------------------------------- /resources/progress.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crowdin/android-studio-plugin/855a24eb0c5c00d0e0be465af8438ce88717c692/resources/progress.gif -------------------------------------------------------------------------------- /resources/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crowdin/android-studio-plugin/855a24eb0c5c00d0e0be465af8438ce88717c692/resources/settings.png -------------------------------------------------------------------------------- /resources/upload.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crowdin/android-studio-plugin/855a24eb0c5c00d0e0be465af8438ce88717c692/resources/upload.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'Crowdin' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/com/crowdin/Constants.java: -------------------------------------------------------------------------------- 1 | package com.crowdin; 2 | 3 | import java.util.ResourceBundle; 4 | 5 | public final class Constants { 6 | 7 | public static final String CONFIG_FILE = "crowdin.yml"; 8 | 9 | public static final ResourceBundle MESSAGES_BUNDLE = ResourceBundle.getBundle("messages/messages"); 10 | 11 | //UI Components Ids 12 | 13 | public static final String PROGRESS_TOOLBAR_ID = "Crowdin.TranslationProgressToolbar"; 14 | public static final String TOOLWINDOW_ID = "Crowdin"; 15 | public static final String UPLOAD_TOOLBAR_ID = "Crowdin.UploadToolbar"; 16 | public static final String DOWNLOAD_TOOLBAR_ID = "Crowdin.DownloadToolbar"; 17 | 18 | //Actions Ids 19 | 20 | public static final String PROGRESS_REFRESH_ACTION = "Crowdin.RefreshTranslationProgressAction"; 21 | public static final String UPLOAD_REFRESH_ACTION = "Crowdin.RefreshUploadAction"; 22 | public static final String DOWNLOAD_REFRESH_ACTION = "Crowdin.RefreshDownloadAction"; 23 | public static final String DOWNLOAD_TRANSLATIONS_ACTION = "Crowdin.DownloadTranslations"; 24 | public static final String DOWNLOAD_SOURCES_ACTION = "Crowdin.DownloadSources"; 25 | public static final String BUNDLE_SETTINGS_ACTION = "Crowdin.BundleSettings"; 26 | public static final String PROGRESS_GROUP_FILES_BY_FILE_ACTION = "Crowdin.GroupByFiles"; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/crowdin/action/ActionContext.java: -------------------------------------------------------------------------------- 1 | package com.crowdin.action; 2 | 3 | import com.crowdin.client.Crowdin; 4 | import com.crowdin.client.config.CrowdinConfig; 5 | import com.crowdin.client.sourcefiles.model.Branch; 6 | import com.crowdin.service.CrowdinProjectCacheProvider; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | 9 | public class ActionContext { 10 | 11 | public final String branchName; 12 | public final Branch branch; 13 | public final VirtualFile root; 14 | public final CrowdinConfig properties; 15 | public final Crowdin crowdin; 16 | public final CrowdinProjectCacheProvider.CrowdinProjectCache crowdinProjectCache; 17 | 18 | public ActionContext(String branchName, Branch branch, VirtualFile root, CrowdinConfig properties, Crowdin crowdin, CrowdinProjectCacheProvider.CrowdinProjectCache crowdinProjectCache) { 19 | this.branchName = branchName; 20 | this.branch = branch; 21 | this.root = root; 22 | this.properties = properties; 23 | this.crowdin = crowdin; 24 | this.crowdinProjectCache = crowdinProjectCache; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/crowdin/action/BackgroundAction.java: -------------------------------------------------------------------------------- 1 | package com.crowdin.action; 2 | 3 | import com.crowdin.client.Crowdin; 4 | import com.crowdin.client.config.CrowdinConfig; 5 | import com.crowdin.client.config.CrowdinPropertiesLoader; 6 | import com.crowdin.client.sourcefiles.model.Branch; 7 | import com.crowdin.logic.BranchLogic; 8 | import com.crowdin.service.CrowdinProjectCacheProvider; 9 | import com.crowdin.settings.CrowdingSettingsState; 10 | import com.crowdin.ui.dialog.ConfirmActionDialog; 11 | import com.crowdin.util.FileUtil; 12 | import com.crowdin.util.NotificationUtil; 13 | import com.crowdin.util.StringUtils; 14 | import com.intellij.openapi.actionSystem.AnAction; 15 | import com.intellij.openapi.actionSystem.AnActionEvent; 16 | import com.intellij.openapi.application.ApplicationManager; 17 | import com.intellij.openapi.progress.ProgressIndicator; 18 | import com.intellij.openapi.progress.ProgressManager; 19 | import com.intellij.openapi.progress.Task; 20 | import com.intellij.openapi.project.Project; 21 | import com.intellij.openapi.vfs.VirtualFile; 22 | import org.jetbrains.annotations.NotNull; 23 | 24 | import javax.swing.*; 25 | import java.util.Optional; 26 | import java.util.concurrent.atomic.AtomicReference; 27 | 28 | import static com.crowdin.Constants.MESSAGES_BUNDLE; 29 | 30 | public abstract class BackgroundAction extends AnAction { 31 | 32 | public BackgroundAction() { 33 | } 34 | 35 | public BackgroundAction(String text, String description, Icon icon) { 36 | super(text, description, icon); 37 | } 38 | 39 | protected abstract void performInBackground(@NotNull AnActionEvent e, @NotNull ProgressIndicator indicator); 40 | 41 | protected abstract String loadingText(AnActionEvent e); 42 | 43 | protected Optional prepare( 44 | Project project, 45 | ProgressIndicator indicator, 46 | boolean checkForManagerAccess, 47 | boolean createBranchIfNotExists, 48 | boolean realodCrowdinCache, 49 | String question, 50 | String okBtn 51 | ) { 52 | VirtualFile root = FileUtil.getProjectBaseDir(project); 53 | 54 | CrowdingSettingsState crowdinSettings = CrowdingSettingsState.getInstance(project); 55 | 56 | if (!StringUtils.isEmpty(question) && !StringUtils.isEmpty(okBtn)) { 57 | boolean confirmation = confirmDialog(project, crowdinSettings, MESSAGES_BUNDLE.getString(question), okBtn); 58 | if (!confirmation) { 59 | return Optional.empty(); 60 | } 61 | if (indicator != null) { 62 | indicator.checkCanceled(); 63 | } 64 | } 65 | 66 | CrowdinConfig properties; 67 | try { 68 | properties = CrowdinPropertiesLoader.load(project); 69 | } catch (Exception e) { 70 | NotificationUtil.showErrorMessage(project, e.getMessage()); 71 | return Optional.empty(); 72 | } 73 | NotificationUtil.setLogDebugLevel(properties.isDebug()); 74 | NotificationUtil.logDebugMessage(project, MESSAGES_BUNDLE.getString("messages.debug.started_action")); 75 | 76 | Crowdin crowdin = new Crowdin(properties.getProjectId(), properties.getApiToken(), properties.getBaseUrl()); 77 | 78 | BranchLogic branchLogic = new BranchLogic(crowdin, project, properties); 79 | String branchName = branchLogic.acquireBranchName(); 80 | if (indicator != null) { 81 | indicator.checkCanceled(); 82 | } 83 | 84 | CrowdinProjectCacheProvider.CrowdinProjectCache crowdinProjectCache = 85 | project.getService(CrowdinProjectCacheProvider.class).getInstance(crowdin, branchName, realodCrowdinCache); 86 | 87 | if (checkForManagerAccess) { 88 | if (!crowdinProjectCache.isManagerAccess()) { 89 | NotificationUtil.showErrorMessage(project, "You need to have manager access to perform this action"); 90 | return Optional.empty(); 91 | } 92 | } 93 | 94 | Branch branch = branchLogic.getBranch(crowdinProjectCache, createBranchIfNotExists); 95 | 96 | if (indicator != null) { 97 | indicator.checkCanceled(); 98 | } 99 | 100 | return Optional.of(new ActionContext(branchName, branch, root, properties, crowdin, crowdinProjectCache)); 101 | } 102 | 103 | @Override 104 | public void actionPerformed(@NotNull AnActionEvent e) { 105 | ProgressManager.getInstance().run(new Task.Backgroundable(e.getProject(), "Crowdin") { 106 | @Override 107 | public void run(@NotNull ProgressIndicator indicator) { 108 | indicator.setText(loadingText(e)); 109 | performInBackground(e, indicator); 110 | } 111 | }); 112 | } 113 | 114 | private boolean confirmDialog(Project project, CrowdingSettingsState settings, String questionText, String okButtonText) { 115 | if (ApplicationManager.getApplication().isHeadlessEnvironment()) { 116 | return true; 117 | } 118 | if (!settings.doNotShowConfirmation) { 119 | AtomicReference confirmation = new AtomicReference<>(); 120 | ApplicationManager.getApplication().invokeAndWait(() -> { 121 | ConfirmActionDialog confirmDialog = 122 | new ConfirmActionDialog(project, questionText, okButtonText); 123 | confirmation.set(confirmDialog.showAndGet()); 124 | settings.doNotShowConfirmation = confirmDialog.isDoNotAskAgain(); 125 | }); 126 | return confirmation.get(); 127 | } else { 128 | return true; 129 | } 130 | 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/crowdin/action/BundleSettingsAction.java: -------------------------------------------------------------------------------- 1 | package com.crowdin.action; 2 | 3 | import com.crowdin.service.ProjectService; 4 | import com.crowdin.ui.panel.download.DownloadWindow; 5 | import com.intellij.icons.AllIcons; 6 | import com.intellij.ide.BrowserUtil; 7 | import com.intellij.openapi.actionSystem.AnAction; 8 | import com.intellij.openapi.actionSystem.AnActionEvent; 9 | import com.intellij.openapi.project.Project; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import static com.crowdin.Constants.TOOLWINDOW_ID; 13 | 14 | public class BundleSettingsAction extends AnAction { 15 | 16 | private boolean enabled = false; 17 | private boolean visible = false; 18 | private String text = ""; 19 | 20 | public BundleSettingsAction() { 21 | super("Bundle Settings", "Bundle Settings", AllIcons.General.Settings); 22 | } 23 | 24 | @Override 25 | public void update(@NotNull AnActionEvent e) { 26 | if (e.getPlace().equals(TOOLWINDOW_ID)) { 27 | this.enabled = e.getPresentation().isEnabled(); 28 | this.visible = e.getPresentation().isVisible(); 29 | this.text = e.getPresentation().getText(); 30 | } 31 | e.getPresentation().setEnabled(enabled); 32 | e.getPresentation().setVisible(visible); 33 | e.getPresentation().setText(text); 34 | } 35 | 36 | @Override 37 | public void actionPerformed(@NotNull AnActionEvent anActionEvent) { 38 | Project project = anActionEvent.getProject(); 39 | if (project == null) { 40 | return; 41 | } 42 | 43 | DownloadWindow window = project.getService(ProjectService.class).getDownloadWindow(); 44 | if (window == null || window.getSelectedBundle() == null) { 45 | return; 46 | } 47 | 48 | String link = window.buildBundleUrl(); 49 | BrowserUtil.browse(link); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/crowdin/action/DownloadAction.java: -------------------------------------------------------------------------------- 1 | package com.crowdin.action; 2 | 3 | import com.crowdin.client.bundles.model.Bundle; 4 | import com.crowdin.logic.DownloadBundleLogic; 5 | import com.crowdin.logic.DownloadTranslationsLogic; 6 | import com.crowdin.service.ProjectService; 7 | import com.crowdin.ui.panel.CrowdinPanelWindowFactory; 8 | import com.crowdin.ui.panel.download.DownloadWindow; 9 | import com.crowdin.util.FileUtil; 10 | import com.crowdin.util.NotificationUtil; 11 | import com.intellij.openapi.actionSystem.AnActionEvent; 12 | import com.intellij.openapi.application.ApplicationManager; 13 | import com.intellij.openapi.progress.ProcessCanceledException; 14 | import com.intellij.openapi.progress.ProgressIndicator; 15 | import com.intellij.openapi.project.Project; 16 | import com.intellij.openapi.vfs.VirtualFile; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | import java.nio.file.Path; 20 | import java.nio.file.Paths; 21 | import java.util.Collections; 22 | import java.util.List; 23 | import java.util.Optional; 24 | import java.util.concurrent.atomic.AtomicBoolean; 25 | 26 | import static com.crowdin.Constants.MESSAGES_BUNDLE; 27 | import static com.crowdin.Constants.TOOLWINDOW_ID; 28 | 29 | public class DownloadAction extends BackgroundAction { 30 | 31 | private boolean enabled = false; 32 | private boolean visible = false; 33 | private String text = ""; 34 | 35 | private final AtomicBoolean isInProgress = new AtomicBoolean(false); 36 | 37 | @Override 38 | public void update(@NotNull AnActionEvent e) { 39 | if (e.getPlace().equals(TOOLWINDOW_ID)) { 40 | this.enabled = e.getPresentation().isEnabled(); 41 | this.visible = e.getPresentation().isVisible(); 42 | this.text = e.getPresentation().getText(); 43 | } 44 | e.getPresentation().setEnabled(!isInProgress.get() && enabled); 45 | e.getPresentation().setVisible(visible); 46 | e.getPresentation().setText(text); 47 | } 48 | 49 | @Override 50 | public void performInBackground(AnActionEvent anActionEvent, @NotNull ProgressIndicator indicator) { 51 | Project project = anActionEvent.getProject(); 52 | 53 | if (project == null) { 54 | return; 55 | } 56 | 57 | isInProgress.set(true); 58 | try { 59 | Optional context = super.prepare( 60 | project, 61 | indicator, 62 | true, 63 | false, 64 | true, 65 | "messages.confirm.download", 66 | "Download" 67 | ); 68 | 69 | if (context.isEmpty()) { 70 | return; 71 | } 72 | 73 | if (context.get().crowdinProjectCache.isStringsBased()) { 74 | if (context.get().branch == null) { 75 | NotificationUtil.showErrorMessage(project, "Branch is missing in configuration file"); 76 | return; 77 | } 78 | 79 | DownloadWindow window = project.getService(ProjectService.class).getDownloadWindow(); 80 | if (window == null) { 81 | return; 82 | } 83 | 84 | Bundle bundle = window.getSelectedBundle(); 85 | 86 | if (bundle == null) { 87 | NotificationUtil.showErrorMessage(project, "Bundle not selected"); 88 | return; 89 | } 90 | 91 | (new DownloadBundleLogic(project, context.get().crowdin, context.get().root, bundle)).process(); 92 | return; 93 | } 94 | 95 | List selectedFiles = Optional 96 | .ofNullable(project.getService(ProjectService.class).getDownloadWindow()) 97 | .map(DownloadWindow::getSelectedFiles) 98 | .orElse(Collections.emptyList()) 99 | .stream() 100 | .map(str -> Paths.get(context.get().root.getPath(), str)) 101 | .toList(); 102 | 103 | if (!selectedFiles.isEmpty()) { 104 | for (Path file : selectedFiles) { 105 | try { 106 | VirtualFile virtualFile = FileUtil.findVFileByPath(file); 107 | DownloadTranslationFromContextAction.performDownload(this, context.get(), virtualFile); 108 | } catch (Exception e) { 109 | NotificationUtil.logErrorMessage(project, e); 110 | NotificationUtil.showWarningMessage(project, e.getMessage()); 111 | } 112 | } 113 | return; 114 | } 115 | 116 | 117 | (new DownloadTranslationsLogic(project, context.get().crowdin, context.get().properties, context.get().root, context.get().crowdinProjectCache, context.get().branch)).process(); 118 | } catch (ProcessCanceledException e) { 119 | throw e; 120 | } catch (Exception e) { 121 | NotificationUtil.logErrorMessage(project, e); 122 | NotificationUtil.showErrorMessage(project, e.getMessage()); 123 | } finally { 124 | isInProgress.set(false); 125 | ApplicationManager.getApplication().invokeAndWait(() -> CrowdinPanelWindowFactory.reloadPanels(project, false)); 126 | } 127 | } 128 | 129 | @Override 130 | protected String loadingText(AnActionEvent e) { 131 | return MESSAGES_BUNDLE.getString("labels.loading_text.download"); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/com/crowdin/action/DownloadSourceFromContextAction.java: -------------------------------------------------------------------------------- 1 | package com.crowdin.action; 2 | 3 | import com.crowdin.client.config.CrowdinPropertiesLoader; 4 | import com.crowdin.logic.ContextLogic; 5 | import com.crowdin.ui.panel.CrowdinPanelWindowFactory; 6 | import com.crowdin.util.FileUtil; 7 | import com.crowdin.util.NotificationUtil; 8 | import com.intellij.openapi.actionSystem.AnActionEvent; 9 | import com.intellij.openapi.actionSystem.CommonDataKeys; 10 | import com.intellij.openapi.application.ApplicationManager; 11 | import com.intellij.openapi.progress.ProcessCanceledException; 12 | import com.intellij.openapi.progress.ProgressIndicator; 13 | import com.intellij.openapi.project.Project; 14 | import com.intellij.openapi.vfs.VirtualFile; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | import java.net.URL; 18 | import java.util.Objects; 19 | import java.util.Optional; 20 | 21 | import static com.crowdin.Constants.MESSAGES_BUNDLE; 22 | 23 | public class DownloadSourceFromContextAction extends BackgroundAction { 24 | 25 | @Override 26 | protected void performInBackground(@NotNull AnActionEvent anActionEvent, @NotNull ProgressIndicator indicator) { 27 | final VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(anActionEvent.getDataContext()); 28 | if (file == null) { 29 | return; 30 | } 31 | 32 | Project project = anActionEvent.getProject(); 33 | if (project == null) { 34 | return; 35 | } 36 | 37 | try { 38 | Optional context = super.prepare( 39 | project, 40 | indicator, 41 | false, 42 | false, 43 | true, 44 | "messages.confirm.download_sources", 45 | "Download" 46 | ); 47 | 48 | if (context.isEmpty()) { 49 | return; 50 | } 51 | 52 | performDownload(this, file, context.get()); 53 | NotificationUtil.showInformationMessage(project, MESSAGES_BUNDLE.getString("messages.success.download_source")); 54 | } catch (ProcessCanceledException e) { 55 | throw e; 56 | } catch (Exception e) { 57 | NotificationUtil.logErrorMessage(project, e); 58 | NotificationUtil.showErrorMessage(project, e.getMessage()); 59 | } finally { 60 | ApplicationManager.getApplication().invokeAndWait(() -> CrowdinPanelWindowFactory.reloadPanels(project, false)); 61 | } 62 | } 63 | 64 | public static void performDownload(Object requestor, VirtualFile file, ActionContext context) { 65 | Long sourceId = ContextLogic.findSourceIdFromSourceFile(context.properties, context.crowdinProjectCache.getFileInfos(context.branch), file, context.root); 66 | URL url = context.crowdin.downloadFile(sourceId); 67 | FileUtil.downloadFile(requestor, file, url); 68 | } 69 | 70 | @Override 71 | public void update(AnActionEvent e) { 72 | Project project = e.getProject(); 73 | if (project == null) { 74 | return; 75 | } 76 | final VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(e.getDataContext()); 77 | boolean isSourceFile = false; 78 | try { 79 | if (CrowdinPropertiesLoader.isWorkspaceNotPrepared(project)) { 80 | return; 81 | } 82 | 83 | Optional context = super.prepare(project, null, false, false, false, null, null); 84 | 85 | if (context.isEmpty()) { 86 | return; 87 | } 88 | 89 | //hide for SB 90 | isSourceFile = !context.get().crowdinProjectCache.isStringsBased() && context.get().properties.getFiles() 91 | .stream() 92 | .flatMap(fb -> FileUtil.getSourceFilesRec(FileUtil.getProjectBaseDir(project), fb.getSource()).stream()) 93 | .anyMatch(f -> Objects.equals(file, f)); 94 | } catch (Exception exception) { 95 | // do nothing 96 | } finally { 97 | e.getPresentation().setEnabled(isSourceFile); 98 | e.getPresentation().setVisible(isSourceFile); 99 | } 100 | } 101 | 102 | @Override 103 | protected String loadingText(AnActionEvent e) { 104 | VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(e.getDataContext()); 105 | return String.format(MESSAGES_BUNDLE.getString("labels.loading_text.download_source_file_from_context"), (file != null ? file.getName() : "")); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/crowdin/action/DownloadTranslationFromContextAction.java: -------------------------------------------------------------------------------- 1 | package com.crowdin.action; 2 | 3 | import com.crowdin.client.RequestBuilder; 4 | import com.crowdin.client.config.CrowdinPropertiesLoader; 5 | import com.crowdin.client.languages.model.Language; 6 | import com.crowdin.logic.ContextLogic; 7 | import com.crowdin.ui.panel.CrowdinPanelWindowFactory; 8 | import com.crowdin.util.FileUtil; 9 | import com.crowdin.util.NotificationUtil; 10 | import com.intellij.openapi.actionSystem.AnActionEvent; 11 | import com.intellij.openapi.actionSystem.CommonDataKeys; 12 | import com.intellij.openapi.application.ApplicationManager; 13 | import com.intellij.openapi.progress.ProcessCanceledException; 14 | import com.intellij.openapi.progress.ProgressIndicator; 15 | import com.intellij.openapi.project.Project; 16 | import com.intellij.openapi.vfs.VirtualFile; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | import java.net.URL; 20 | import java.util.Map; 21 | import java.util.Optional; 22 | 23 | import static com.crowdin.Constants.MESSAGES_BUNDLE; 24 | 25 | public class DownloadTranslationFromContextAction extends BackgroundAction { 26 | 27 | @Override 28 | protected void performInBackground(@NotNull AnActionEvent anActionEvent, @NotNull ProgressIndicator indicator) { 29 | final VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(anActionEvent.getDataContext()); 30 | 31 | if (file == null) { 32 | return; 33 | } 34 | 35 | Project project = anActionEvent.getProject(); 36 | 37 | if (project == null) { 38 | return; 39 | } 40 | 41 | try { 42 | 43 | Optional context = super.prepare( 44 | project, 45 | indicator, 46 | true, 47 | false, 48 | true, 49 | "messages.confirm.download", 50 | "Download" 51 | ); 52 | 53 | if (context.isEmpty()) { 54 | return; 55 | } 56 | 57 | performDownload(this, context.get(), file); 58 | } catch (ProcessCanceledException e) { 59 | throw e; 60 | } catch (Exception e) { 61 | NotificationUtil.logErrorMessage(project, e); 62 | NotificationUtil.showErrorMessage(project, e.getMessage()); 63 | } finally { 64 | ApplicationManager.getApplication().invokeAndWait(() -> CrowdinPanelWindowFactory.reloadPanels(project, false)); 65 | } 66 | } 67 | 68 | public static void performDownload(Object requestor, ActionContext context, VirtualFile file) { 69 | Map.Entry source = ContextLogic.findSourceFileFromTranslationFile(file, context.properties, context.root, context.crowdinProjectCache) 70 | .orElseThrow(() -> new RuntimeException(MESSAGES_BUNDLE.getString("errors.file_no_representative_context"))); 71 | 72 | Long sourceId = ContextLogic.findSourceIdFromSourceFile(context.properties, context.crowdinProjectCache.getFileInfos(context.branch), source.getKey(), context.root); 73 | 74 | URL url = context.crowdin.downloadFileTranslation(sourceId, RequestBuilder.buildProjectFileTranslation(source.getValue().getId())); 75 | FileUtil.downloadFile(requestor, file, url); 76 | } 77 | 78 | @Override 79 | public void update(AnActionEvent e) { 80 | Project project = e.getProject(); 81 | final VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(e.getDataContext()); 82 | boolean isTranslationFile = false; 83 | 84 | try { 85 | if (file == null) { 86 | return; 87 | } 88 | 89 | if (CrowdinPropertiesLoader.isWorkspaceNotPrepared(project)) { 90 | return; 91 | } 92 | 93 | Optional context = super.prepare(project, null, false, false, false, null, null); 94 | 95 | if (context.isEmpty()) { 96 | return; 97 | } 98 | 99 | //hide for SB 100 | isTranslationFile = !context.get().crowdinProjectCache.isStringsBased() && ContextLogic.findSourceFileFromTranslationFile(file, context.get().properties, context.get().root, context.get().crowdinProjectCache).isPresent(); 101 | } catch (Exception exception) { 102 | // do nothing 103 | } finally { 104 | e.getPresentation().setEnabled(isTranslationFile); 105 | e.getPresentation().setVisible(isTranslationFile); 106 | } 107 | } 108 | 109 | @Override 110 | protected String loadingText(AnActionEvent e) { 111 | return MESSAGES_BUNDLE.getString("labels.loading_text.download"); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/crowdin/action/SettingsAction.java: -------------------------------------------------------------------------------- 1 | package com.crowdin.action; 2 | 3 | import com.crowdin.settings.CrowdinSettingsConfigurable; 4 | import com.intellij.icons.AllIcons; 5 | import com.intellij.openapi.actionSystem.AnAction; 6 | import com.intellij.openapi.actionSystem.AnActionEvent; 7 | import com.intellij.openapi.options.ShowSettingsUtil; 8 | import com.intellij.openapi.project.Project; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public class SettingsAction extends AnAction { 12 | 13 | public SettingsAction() { 14 | super("Settings", "Plugin settings", AllIcons.General.Settings); 15 | } 16 | 17 | @Override 18 | public void actionPerformed(@NotNull AnActionEvent anActionEvent) { 19 | Project project = anActionEvent.getProject(); 20 | if (project == null) { 21 | return; 22 | } 23 | 24 | ShowSettingsUtil.getInstance().showSettingsDialog(project, CrowdinSettingsConfigurable.class); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/crowdin/action/UploadAction.java: -------------------------------------------------------------------------------- 1 | package com.crowdin.action; 2 | 3 | import com.crowdin.client.config.FileBean; 4 | import com.crowdin.logic.SourceLogic; 5 | import com.crowdin.service.CrowdinProjectCacheProvider; 6 | import com.crowdin.service.ProjectService; 7 | import com.crowdin.ui.panel.CrowdinPanelWindowFactory; 8 | import com.crowdin.ui.panel.upload.UploadWindow; 9 | import com.crowdin.util.FileUtil; 10 | import com.crowdin.util.NotificationUtil; 11 | import com.intellij.openapi.actionSystem.AnActionEvent; 12 | import com.intellij.openapi.application.ApplicationManager; 13 | import com.intellij.openapi.progress.ProcessCanceledException; 14 | import com.intellij.openapi.progress.ProgressIndicator; 15 | import com.intellij.openapi.project.Project; 16 | import com.intellij.openapi.vfs.VirtualFile; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | import java.nio.file.Path; 20 | import java.nio.file.Paths; 21 | import java.util.AbstractMap; 22 | import java.util.Collections; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Optional; 26 | import java.util.stream.Collectors; 27 | 28 | import static com.crowdin.Constants.MESSAGES_BUNDLE; 29 | 30 | /** 31 | * Created by ihor on 1/10/17. 32 | */ 33 | @SuppressWarnings("ALL") 34 | public class UploadAction extends BackgroundAction { 35 | @Override 36 | public void performInBackground(@NotNull final AnActionEvent anActionEvent, ProgressIndicator indicator) { 37 | Project project = anActionEvent.getProject(); 38 | if (project == null) { 39 | return; 40 | } 41 | 42 | try { 43 | Optional context = this.prepare( 44 | project, 45 | indicator, 46 | false, 47 | true, 48 | true, 49 | "messages.confirm.upload_sources", 50 | "Upload" 51 | ); 52 | 53 | if (context.isEmpty()) { 54 | return; 55 | } 56 | 57 | List selectedFiles = Optional 58 | .ofNullable(project.getService(ProjectService.class).getUploadWindow()) 59 | .map(UploadWindow::getSelectedFiles) 60 | .orElse(Collections.emptyList()) 61 | .stream() 62 | .map(str -> Paths.get(context.get().root.getPath(), str)) 63 | .toList(); 64 | 65 | NotificationUtil.logDebugMessage(project, MESSAGES_BUNDLE.getString("messages.debug.upload_sources.list_of_patterns") 66 | + context.get().properties.getFiles().stream() 67 | .map(fileBean -> String.format(MESSAGES_BUNDLE.getString("messages.debug.upload_sources.list_of_patterns_item"), fileBean.getSource(), fileBean.getTranslation())) 68 | .collect(Collectors.joining())); 69 | 70 | Map> sources = context.get().properties.getFiles() 71 | .stream() 72 | .map(fileBean -> new AbstractMap.SimpleEntry<>( 73 | fileBean, 74 | FileUtil.getSourceFilesRec(context.get().root, fileBean.getSource()) 75 | .stream() 76 | .filter(f -> selectedFiles.isEmpty() || selectedFiles.contains(Paths.get(f.getPath()))) 77 | .toList() 78 | ) 79 | ) 80 | .filter(e -> !e.getValue().isEmpty()) 81 | .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); 82 | SourceLogic.processSources(project, context.get().root, context.get().crowdin, context.get().crowdinProjectCache, context.get().branch, context.get().properties.isPreserveHierarchy(), sources); 83 | 84 | project.getService(CrowdinProjectCacheProvider.class).outdateBranch(context.get().branchName); 85 | } catch (ProcessCanceledException e) { 86 | throw e; 87 | } catch (Exception e) { 88 | NotificationUtil.logErrorMessage(project, e); 89 | NotificationUtil.showErrorMessage(project, e.getMessage()); 90 | } finally { 91 | ApplicationManager.getApplication().invokeAndWait(() -> CrowdinPanelWindowFactory.reloadPanels(project, true)); 92 | } 93 | } 94 | 95 | @Override 96 | protected String loadingText(AnActionEvent e) { 97 | return MESSAGES_BUNDLE.getString("labels.loading_text.upload_sources"); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/crowdin/action/UploadFromContextAction.java: -------------------------------------------------------------------------------- 1 | package com.crowdin.action; 2 | 3 | import com.crowdin.client.config.CrowdinConfig; 4 | import com.crowdin.client.config.CrowdinPropertiesLoader; 5 | import com.crowdin.client.config.FileBean; 6 | import com.crowdin.logic.SourceLogic; 7 | import com.crowdin.service.CrowdinProjectCacheProvider; 8 | import com.crowdin.ui.panel.CrowdinPanelWindowFactory; 9 | import com.crowdin.util.FileUtil; 10 | import com.crowdin.util.NotificationUtil; 11 | import com.intellij.openapi.actionSystem.AnActionEvent; 12 | import com.intellij.openapi.actionSystem.CommonDataKeys; 13 | import com.intellij.openapi.application.ApplicationManager; 14 | import com.intellij.openapi.progress.ProcessCanceledException; 15 | import com.intellij.openapi.progress.ProgressIndicator; 16 | import com.intellij.openapi.project.Project; 17 | import com.intellij.openapi.vfs.VirtualFile; 18 | import org.jetbrains.annotations.NotNull; 19 | 20 | import java.util.Collections; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.Objects; 24 | import java.util.Optional; 25 | 26 | import static com.crowdin.Constants.MESSAGES_BUNDLE; 27 | 28 | /** 29 | * Created by ihor on 1/27/17. 30 | */ 31 | public class UploadFromContextAction extends BackgroundAction { 32 | @Override 33 | public void performInBackground(AnActionEvent anActionEvent, @NotNull ProgressIndicator indicator) { 34 | final VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(anActionEvent.getDataContext()); 35 | Project project = anActionEvent.getProject(); 36 | if (project == null) { 37 | return; 38 | } 39 | 40 | try { 41 | Optional context = this.prepare( 42 | project, 43 | indicator, 44 | false, 45 | true, 46 | true, 47 | "messages.confirm.upload_source_file", 48 | "Upload" 49 | ); 50 | 51 | if (context.isEmpty()) { 52 | return; 53 | } 54 | 55 | FileBean foundFileBean = context.get().properties.getFiles() 56 | .stream() 57 | .filter(fb -> FileUtil.getSourceFilesRec(context.get().root, fb.getSource()).contains(file)) 58 | .findAny() 59 | .orElseThrow(() -> new RuntimeException("Unexpected error: couldn't find suitable source pattern")); 60 | 61 | indicator.checkCanceled(); 62 | 63 | Map> source = Collections.singletonMap(foundFileBean, Collections.singletonList(file)); 64 | SourceLogic.processSources(project, context.get().root, context.get().crowdin, context.get().crowdinProjectCache, context.get().branch, context.get().properties.isPreserveHierarchy(), source); 65 | 66 | project.getService(CrowdinProjectCacheProvider.class).outdateBranch(context.get().branchName); 67 | } catch (ProcessCanceledException e) { 68 | throw e; 69 | } catch (Exception e) { 70 | NotificationUtil.logErrorMessage(project, e); 71 | NotificationUtil.showErrorMessage(project, e.getMessage()); 72 | } finally { 73 | ApplicationManager.getApplication().invokeAndWait(() -> CrowdinPanelWindowFactory.reloadPanels(project, true)); 74 | } 75 | } 76 | 77 | @Override 78 | public void update(AnActionEvent e) { 79 | Project project = e.getProject(); 80 | final VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(e.getDataContext()); 81 | boolean isSourceFile = false; 82 | try { 83 | if (CrowdinPropertiesLoader.isWorkspaceNotPrepared(project)) { 84 | return; 85 | } 86 | CrowdinConfig properties; 87 | properties = CrowdinPropertiesLoader.load(project); 88 | isSourceFile = properties.getFiles() 89 | .stream() 90 | .flatMap(fb -> FileUtil.getSourceFilesRec(FileUtil.getProjectBaseDir(project), fb.getSource()).stream()) 91 | .anyMatch(f -> Objects.equals(file, f)); 92 | } catch (Exception exception) { 93 | // do nothing 94 | } finally { 95 | e.getPresentation().setEnabled(isSourceFile); 96 | e.getPresentation().setVisible(isSourceFile); 97 | } 98 | } 99 | 100 | @Override 101 | protected String loadingText(AnActionEvent e) { 102 | return String.format(MESSAGES_BUNDLE.getString("labels.loading_text.upload_sources_from_context"), CommonDataKeys.VIRTUAL_FILE.getData(e.getDataContext()).getName()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/crowdin/activity/CrowdinStartupActivity.java: -------------------------------------------------------------------------------- 1 | package com.crowdin.activity; 2 | 3 | import com.crowdin.client.Crowdin; 4 | import com.crowdin.client.config.CrowdinConfig; 5 | import com.crowdin.client.config.CrowdinPropertiesLoader; 6 | import com.crowdin.event.FileChangeListener; 7 | import com.crowdin.logic.BranchLogic; 8 | import com.crowdin.service.CrowdinProjectCacheProvider; 9 | import com.crowdin.service.ProjectService; 10 | import com.crowdin.ui.panel.CrowdinPanelWindowFactory; 11 | import com.crowdin.util.NotificationUtil; 12 | import com.intellij.openapi.progress.ProgressIndicator; 13 | import com.intellij.openapi.progress.ProgressManager; 14 | import com.intellij.openapi.progress.Task; 15 | import com.intellij.openapi.project.Project; 16 | import com.intellij.openapi.startup.StartupActivity; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | import java.util.EnumSet; 20 | 21 | public class CrowdinStartupActivity implements StartupActivity { 22 | 23 | @Override 24 | public void runActivity(@NotNull Project project) { 25 | try { 26 | new FileChangeListener(project); 27 | 28 | if (CrowdinPropertiesLoader.isWorkspaceNotPrepared(project)) { 29 | return; 30 | } 31 | 32 | //config validation 33 | CrowdinConfig properties = CrowdinPropertiesLoader.load(project); 34 | Crowdin crowdin = new Crowdin(properties.getProjectId(), properties.getApiToken(), properties.getBaseUrl()); 35 | 36 | this.reloadPlugin(project, crowdin, properties); 37 | } catch (Exception e) { 38 | NotificationUtil.showErrorMessage(project, e.getMessage()); 39 | } 40 | } 41 | 42 | private void reloadPlugin(Project project, Crowdin crowdin, CrowdinConfig properties) { 43 | BranchLogic branchLogic = new BranchLogic(crowdin, project, properties); 44 | String branchName = branchLogic.acquireBranchName(); 45 | 46 | ProgressManager.getInstance().run(new Task.Backgroundable(project, "Crowdin") { 47 | @Override 48 | public void run(@NotNull ProgressIndicator indicator) { 49 | try { 50 | indicator.setText("Updating Crowdin cache"); 51 | project.getService(CrowdinProjectCacheProvider.class).getInstance(crowdin, branchName, true); 52 | ProjectService service = project.getService(ProjectService.class); 53 | EnumSet loadedComponents = service.addAndGetLoadedComponents(ProjectService.InitializationItem.STARTUP_ACTIVITY); 54 | if (loadedComponents.contains(ProjectService.InitializationItem.UI_PANELS)) { 55 | CrowdinPanelWindowFactory.reloadPanels(project, true); 56 | } 57 | } catch (Exception e) { 58 | NotificationUtil.showErrorMessage(project, e.getMessage()); 59 | } 60 | } 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/crowdin/client/BranchInfo.java: -------------------------------------------------------------------------------- 1 | package com.crowdin.client; 2 | 3 | public record BranchInfo(String name, String title) { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/crowdin/client/CrowdinClient.java: -------------------------------------------------------------------------------- 1 | package com.crowdin.client; 2 | 3 | import com.crowdin.client.bundles.model.Bundle; 4 | import com.crowdin.client.bundles.model.BundleExport; 5 | import com.crowdin.client.core.model.PatchRequest; 6 | import com.crowdin.client.labels.model.AddLabelRequest; 7 | import com.crowdin.client.labels.model.Label; 8 | import com.crowdin.client.languages.model.Language; 9 | import com.crowdin.client.projectsgroups.model.Project; 10 | import com.crowdin.client.sourcefiles.model.AddBranchRequest; 11 | import com.crowdin.client.sourcefiles.model.AddDirectoryRequest; 12 | import com.crowdin.client.sourcefiles.model.AddFileRequest; 13 | import com.crowdin.client.sourcefiles.model.Branch; 14 | import com.crowdin.client.sourcefiles.model.Directory; 15 | import com.crowdin.client.sourcefiles.model.FileInfo; 16 | import com.crowdin.client.sourcefiles.model.UpdateFileRequest; 17 | import com.crowdin.client.sourcestrings.model.SourceString; 18 | import com.crowdin.client.sourcestrings.model.UploadStringsProgress; 19 | import com.crowdin.client.sourcestrings.model.UploadStringsRequest; 20 | import com.crowdin.client.translations.model.BuildProjectFileTranslationRequest; 21 | import com.crowdin.client.translations.model.BuildProjectTranslationRequest; 22 | import com.crowdin.client.translations.model.ProjectBuild; 23 | import com.crowdin.client.translations.model.UploadTranslationsRequest; 24 | import com.crowdin.client.translations.model.UploadTranslationsStringsRequest; 25 | import com.crowdin.client.translationstatus.model.FileBranchProgress; 26 | import com.crowdin.client.translationstatus.model.LanguageProgress; 27 | 28 | import java.io.InputStream; 29 | import java.net.URL; 30 | import java.util.List; 31 | import java.util.Map; 32 | import java.util.Optional; 33 | 34 | /** 35 | * Base type for a Crowdin client. 36 | */ 37 | public interface CrowdinClient { 38 | 39 | Long getProjectId(); 40 | 41 | String getBaseUrl(); 42 | 43 | Long addStorage(String fileName, InputStream content); 44 | 45 | void updateSource(Long sourceId, UpdateFileRequest request); 46 | 47 | URL downloadFile(Long fileId); 48 | 49 | void addSource(AddFileRequest request); 50 | 51 | void editSource(Long fileId, List request); 52 | 53 | void uploadTranslation(String languageId, UploadTranslationsRequest request); 54 | 55 | void uploadStringsTranslation(String languageId, UploadTranslationsStringsRequest request); 56 | 57 | Directory addDirectory(AddDirectoryRequest request); 58 | 59 | Project getProject(); 60 | 61 | List extractProjectLanguages(Project crowdinProject); 62 | 63 | UploadStringsProgress uploadStrings(UploadStringsRequest request); 64 | 65 | UploadStringsProgress checkUploadStringsStatus(String id); 66 | 67 | ProjectBuild startBuildingTranslation(BuildProjectTranslationRequest request); 68 | 69 | ProjectBuild checkBuildingStatus(Long buildId); 70 | 71 | URL downloadProjectTranslations(Long buildId); 72 | 73 | BundleExport startBuildingBundle(Long bundleId); 74 | 75 | BundleExport checkBundleBuildingStatus(Long buildId, String exportId); 76 | 77 | URL downloadBundle(Long buildId, String exportId); 78 | 79 | URL downloadFileTranslation(Long fileId, BuildProjectFileTranslationRequest request); 80 | 81 | List getSupportedLanguages(); 82 | 83 | Map getDirectories(Long branchId); 84 | 85 | List getFiles(Long branchId); 86 | 87 | List getStrings(); 88 | 89 | Branch addBranch(AddBranchRequest request); 90 | 91 | Optional getBranch(String name); 92 | 93 | Map getBranches(); 94 | 95 | List getProjectProgress(); 96 | 97 | List getLanguageProgress(String languageId); 98 | 99 | List