Merge remote-tracking branch 'github/main'

This commit is contained in:
Ilya 2026-02-10 21:28:54 +03:00
commit b24f189da2
42 changed files with 741 additions and 328 deletions

View File

@ -26,54 +26,33 @@ concurrency:
jobs:
# Prepare environment and build the plugin
# Prepare the environment and build the plugin
build:
name: Build
runs-on: ubuntu-latest
outputs:
version: ${{ steps.properties.outputs.version }}
changelog: ${{ steps.properties.outputs.changelog }}
pluginVerifierHomeDir: ${{ steps.properties.outputs.pluginVerifierHomeDir }}
steps:
# Check out current repository
# Free GitHub Actions Environment Disk Space
- name: Maximize Build Space
uses: jlumbroso/free-disk-space@v1.3.1
with:
tool-cache: false
large-packages: false
# Check out the current repository
- name: Fetch Sources
uses: actions/checkout@v4
# Validate wrapper
- name: Gradle Wrapper Validation
uses: gradle/wrapper-validation-action@v2
# Set up Java environment for the next steps
# Set up the Java environment for the next steps
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 17
java-version: 21
# Setup Gradle
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
gradle-home-cache-cleanup: true
# Set environment variables
- name: Export Properties
id: properties
shell: bash
run: |
PROPERTIES="$(./gradlew properties --console=plain -q)"
VERSION="$(echo "$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')"
CHANGELOG="$(./gradlew getChangelog --unreleased --no-header --console=plain -q)"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "pluginVerifierHomeDir=~/.pluginVerifier" >> $GITHUB_OUTPUT
echo "changelog<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
./gradlew listProductsReleases # prepare list of IDEs for Plugin Verifier
uses: gradle/actions/setup-gradle@v4
# Build plugin
- name: Build plugin
@ -90,7 +69,7 @@ jobs:
echo "filename=${FILENAME:0:-4}" >> $GITHUB_OUTPUT
# Store already-built plugin as an artifact for downloading
# Store an already-built plugin as an artifact for downloading
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
@ -104,22 +83,29 @@ jobs:
runs-on: ubuntu-latest
steps:
# Check out current repository
# Free GitHub Actions Environment Disk Space
- name: Maximize Build Space
uses: jlumbroso/free-disk-space@v1.3.1
with:
tool-cache: false
large-packages: false
# Check out the current repository
- name: Fetch Sources
uses: actions/checkout@v4
# Set up Java environment for the next steps
# Set up the Java environment for the next steps
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 17
java-version: 21
# Setup Gradle
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/actions/setup-gradle@v4
with:
gradle-home-cache-cleanup: true
cache-read-only: true
# Run tests
- name: Run Tests
@ -135,11 +121,12 @@ jobs:
# Upload the Kover report to CodeCov
- name: Upload Code Coverage Report
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
files: ${{ github.workspace }}/build/reports/kover/report.xml
token: ${{ secrets.CODECOV_TOKEN }}
# Run Qodana inspections and provide report
# Run Qodana inspections and provide a report
inspectCode:
name: Inspect code
needs: [ build ]
@ -152,25 +139,28 @@ jobs:
# Free GitHub Actions Environment Disk Space
- name: Maximize Build Space
uses: jlumbroso/free-disk-space@main
uses: jlumbroso/free-disk-space@v1.3.1
with:
tool-cache: false
large-packages: false
# Check out current repository
# Check out the current repository
- name: Fetch Sources
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit
fetch-depth: 0 # a full history is required for pull request analysis
# Set up Java environment for the next steps
# Set up the Java environment for the next steps
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 17
java-version: 21
# Run Qodana inspections
- name: Qodana - Code Inspection
uses: JetBrains/qodana-action@v2023.3.1
uses: JetBrains/qodana-action@v2025.1.1
with:
cache-default-branch-only: true
@ -183,38 +173,31 @@ jobs:
# Free GitHub Actions Environment Disk Space
- name: Maximize Build Space
uses: jlumbroso/free-disk-space@main
uses: jlumbroso/free-disk-space@v1.3.1
with:
tool-cache: false
large-packages: false
# Check out current repository
# Check out the current repository
- name: Fetch Sources
uses: actions/checkout@v4
# Set up Java environment for the next steps
# Set up the Java environment for the next steps
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 17
java-version: 21
# Setup Gradle
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/actions/setup-gradle@v4
with:
gradle-home-cache-cleanup: true
# Cache Plugin Verifier IDEs
- name: Setup Plugin Verifier IDEs Cache
uses: actions/cache@v4
with:
path: ${{ needs.build.outputs.pluginVerifierHomeDir }}/ides
key: plugin-verifier-${{ hashFiles('build/listProductsReleases.txt') }}
cache-read-only: true
# Run Verify Plugin task and IntelliJ Plugin Verifier tool
- name: Run Plugin Verification tasks
run: ./gradlew runPluginVerifier -Dplugin.verifier.home.dir=${{ needs.build.outputs.pluginVerifierHomeDir }}
run: ./gradlew verifyPlugin
# Collect Plugin Verifier Result
- name: Collect Plugin Verifier Result
@ -225,7 +208,7 @@ jobs:
path: ${{ github.workspace }}/build/reports/pluginVerifier
# Prepare a draft release for GitHub Releases page for the manual verification
# If accepted and published, release workflow would be triggered
# If accepted and published, the release workflow would be triggered
releaseDraft:
name: Release draft
if: github.event_name != 'pull_request'
@ -235,7 +218,7 @@ jobs:
contents: write
steps:
# Check out current repository
# Check out the current repository
- name: Fetch Sources
uses: actions/checkout@v4
@ -253,10 +236,11 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "v${{ needs.build.outputs.version }}" \
VERSION=$(./gradlew properties --property version --quiet --console=plain | tail -n 1 | cut -f2- -d ' ')
RELEASE_NOTE="./build/tmp/release_note.txt"
./gradlew getChangelog --unreleased --no-header --quiet --console=plain --output-file=$RELEASE_NOTE
gh release create $VERSION \
--draft \
--title "v${{ needs.build.outputs.version }}" \
--notes "$(cat << 'EOM'
${{ needs.build.outputs.changelog }}
EOM
)"
--title $VERSION \
--notes-file $RELEASE_NOTE

View File

@ -1,5 +1,5 @@
# GitHub Actions Workflow created for handling the release process based on the draft release prepared with the Build workflow.
# Running the publishPlugin task requires all following secrets to be provided: PUBLISH_TOKEN, PRIVATE_KEY, PRIVATE_KEY_PASSWORD, CERTIFICATE_CHAIN.
# Running the publishPlugin task requires all the following secrets to be provided: PUBLISH_TOKEN, PRIVATE_KEY, PRIVATE_KEY_PASSWORD, CERTIFICATE_CHAIN.
# See https://plugins.jetbrains.com/docs/intellij/plugin-signing.html for more information.
name: Release
@ -18,46 +18,43 @@ jobs:
pull-requests: write
steps:
# Check out current repository
# Free GitHub Actions Environment Disk Space
- name: Maximize Build Space
uses: jlumbroso/free-disk-space@v1.3.1
with:
tool-cache: false
large-packages: false
# Check out the current repository
- name: Fetch Sources
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
# Set up Java environment for the next steps
# Set up the Java environment for the next steps
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 17
java-version: 21
# Setup Gradle
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/actions/setup-gradle@v4
with:
gradle-home-cache-cleanup: true
cache-read-only: true
# Set environment variables
- name: Export Properties
id: properties
shell: bash
run: |
CHANGELOG="$(cat << 'EOM' | sed -e 's/^[[:space:]]*$//g' -e '/./,$!d'
${{ github.event.release.body }}
EOM
)"
echo "changelog<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# Update Unreleased section with the current release note
# Update the Unreleased section with the current release note
- name: Patch Changelog
if: ${{ steps.properties.outputs.changelog != '' }}
if: ${{ github.event.release.body != '' }}
env:
CHANGELOG: ${{ steps.properties.outputs.changelog }}
CHANGELOG: ${{ github.event.release.body }}
run: |
./gradlew patchChangelog --release-note="$CHANGELOG"
RELEASE_NOTE="./build/tmp/release_note.txt"
mkdir -p "$(dirname "$RELEASE_NOTE")"
echo "$CHANGELOG" > $RELEASE_NOTE
./gradlew patchChangelog --release-note-file=$RELEASE_NOTE
# Publish the plugin to JetBrains Marketplace
- name: Publish Plugin
@ -68,7 +65,7 @@ jobs:
PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }}
run: ./gradlew publishPlugin
# Upload artifact as a release asset
# Upload an artifact as a release asset
- name: Upload Release Asset
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -31,22 +31,22 @@ jobs:
steps:
# Check out current repository
# Check out the current repository
- name: Fetch Sources
uses: actions/checkout@v4
# Set up Java environment for the next steps
# Set up the Java environment for the next steps
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 17
java-version: 21
# Setup Gradle
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/actions/setup-gradle@v4
with:
gradle-home-cache-cleanup: true
cache-read-only: true
# Run IDEA prepared for UI testing
- name: Run IDE
@ -54,7 +54,7 @@ jobs:
# Wait for IDEA to be started
- name: Health Check
uses: jtalk/url-health-check-action@v3
uses: jtalk/url-health-check-action@v4
with:
url: http://127.0.0.1:8082
max-attempts: 15

3
.gitignore vendored
View File

@ -1,4 +1,7 @@
.DS_Store
.gradle
.idea
.intellijPlatform
.kotlin
.qodana
build

View File

@ -1,25 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run IDE for UI Tests" type="GradleRunConfiguration" factoryName="Gradle">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="runIdeForUiTests" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Plugin" type="GradleRunConfiguration" factoryName="Gradle">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
<log_file alias="IDE logs" path="$PROJECT_DIR$/build/idea-sandbox/*/log/idea.log" show_all="true" />
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
@ -19,6 +19,7 @@
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Qodana" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="qodanaScan" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Verifications" type="GradleRunConfiguration" factoryName="Gradle">
<configuration default="false" name="Run Tests" type="GradleRunConfiguration" factoryName="Gradle">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
<ExternalSystemSettings>
<option name="executionName" />
@ -11,7 +11,7 @@
</option>
<option name="taskNames">
<list>
<option value="runPluginVerifier" />
<option value="check" />
</list>
</option>
<option name="vmOptions" value="" />
@ -19,8 +19,7 @@
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2">
<option name="Gradle.BeforeRunTask" enabled="true" tasks="clean" externalProjectPath="$PROJECT_DIR$" vmOptions="" scriptParameters="" />
</method>
<RunAsTest>true</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@ -11,7 +11,7 @@
</option>
<option name="taskNames">
<list>
<option value="runPluginVerifier" />
<option value="verifyPlugin" />
</list>
</option>
<option name="vmOptions" value="" />
@ -19,8 +19,7 @@
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2">
<option name="Gradle.BeforeRunTask" enabled="true" tasks="clean" externalProjectPath="$PROJECT_DIR$" vmOptions="" scriptParameters="" />
</method>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@ -4,6 +4,52 @@
## [Unreleased]
### Changed
- Use textarea component for translation dialog locale values (#477)
## [4.8.1] - 2025-09-04
### Changed
- Update dependencies
## [4.8.0] - 2025-04-25
### Added
- Support for IntelliJ Platform version 2025.1
### Changed
- Dropped support for platform builds below 2024.2
- Build using Java 21
- Update dependencies
## [4.7.0] - 2024-11-23
### Added
- Support for IntelliJ Platform version 2024.3
- Assistance for translation key reference and key completion in HTML files. Thanks to @Adeptius
### Changed
- Require at least IntelliJ Platform version 2023.3.8
- Remove deprecated api usage
## [4.6.0] - 2024-05-31
### Added
- Automated key naming convention suggestion for Localize-It action. Thanks to @JPilson
## [4.5.2] - 2024-05-30
### Added
- Support for IntelliJ Platform version 2024.2
## [4.5.1] - 2024-04-13
### Fixed
@ -453,39 +499,43 @@
- README.md
[Unreleased]: https://github.com/marhali/easy-i18n/compare/v4.5.1...HEAD
[Unreleased]: https://github.com/marhali/easy-i18n/compare/v4.8.0...HEAD
[4.8.0]: https://github.com/marhali/easy-i18n/compare/v4.7.0...v4.8.0
[4.7.0]: https://github.com/marhali/easy-i18n/compare/v4.6.0...v4.7.0
[4.6.0]: https://github.com/marhali/easy-i18n/compare/v4.5.2...v4.6.0
[4.5.2]: https://github.com/marhali/easy-i18n/compare/v4.5.1...v4.5.2
[4.5.1]: https://github.com/marhali/easy-i18n/compare/v4.5.0...v4.5.1
[4.5.0]: https://github.com/marhali/easy-i18n/compare/v4.4.4...v4.5.0
[4.4.4]: https://github.com/marhali/easy-i18n/compare/v4.4.3...v4.4.4
[4.4.3]: https://github.com/marhali/easy-i18n/compare/v4.4.2...v4.4.3
[4.4.2]: https://github.com/marhali/easy-i18n/compare/v4.4.1...v4.4.2
[4.4.1]: null/compare/v4.4.0...v4.4.1
[4.4.0]: null/compare/v4.3.1...v4.4.0
[4.3.1]: null/compare/v4.3.0...v4.3.1
[4.3.0]: null/compare/v4.2.4...v4.3.0
[4.2.4]: null/compare/v4.2.3...v4.2.4
[4.2.3]: null/compare/v4.2.2...v4.2.3
[4.2.2]: null/compare/v4.2.1...v4.2.2
[4.2.1]: null/compare/v4.2.0...v4.2.1
[4.2.0]: null/compare/v4.1.1...v4.2.0
[4.1.1]: null/compare/v4.1.0...v4.1.1
[4.1.0]: null/compare/v4.0.0...v4.1.0
[4.0.0]: null/compare/v3.2.0...v4.0.0
[3.2.0]: null/compare/v3.1.0...v3.2.0
[3.1.0]: null/compare/v3.0.1...v3.1.0
[3.0.1]: null/compare/v3.0.0...v3.0.1
[3.0.0]: null/compare/v2.0.0...v3.0.0
[2.0.0]: null/compare/v1.7.1...v2.0.0
[1.7.1]: null/compare/v1.7.0...v1.7.1
[1.7.0]: null/compare/v1.6.0...v1.7.0
[1.6.0]: null/compare/v1.5.1...v1.6.0
[1.5.1]: null/compare/v1.5.0...v1.5.1
[1.5.0]: null/compare/v1.4.1...v1.5.0
[1.4.1]: null/compare/v1.4.0...v1.4.1
[1.4.0]: null/compare/v1.3.0...v1.4.0
[1.3.0]: null/compare/v1.2.0...v1.3.0
[1.2.0]: null/compare/v1.1.1...v1.2.0
[1.1.1]: null/compare/v1.1.0...v1.1.1
[1.1.0]: null/compare/v1.0.1...v1.1.0
[1.0.1]: null/compare/v1.0.0...v1.0.1
[1.0.0]: null/commits/v1.0.0
[4.4.1]: https://github.com/marhali/easy-i18n/compare/v4.4.0...v4.4.1
[4.4.0]: https://github.com/marhali/easy-i18n/compare/v4.3.1...v4.4.0
[4.3.1]: https://github.com/marhali/easy-i18n/compare/v4.3.0...v4.3.1
[4.3.0]: https://github.com/marhali/easy-i18n/compare/v4.2.4...v4.3.0
[4.2.4]: https://github.com/marhali/easy-i18n/compare/v4.2.3...v4.2.4
[4.2.3]: https://github.com/marhali/easy-i18n/compare/v4.2.2...v4.2.3
[4.2.2]: https://github.com/marhali/easy-i18n/compare/v4.2.1...v4.2.2
[4.2.1]: https://github.com/marhali/easy-i18n/compare/v4.2.0...v4.2.1
[4.2.0]: https://github.com/marhali/easy-i18n/compare/v4.1.1...v4.2.0
[4.1.1]: https://github.com/marhali/easy-i18n/compare/v4.1.0...v4.1.1
[4.1.0]: https://github.com/marhali/easy-i18n/compare/v4.0.0...v4.1.0
[4.0.0]: https://github.com/marhali/easy-i18n/compare/v3.2.0...v4.0.0
[3.2.0]: https://github.com/marhali/easy-i18n/compare/v3.1.0...v3.2.0
[3.1.0]: https://github.com/marhali/easy-i18n/compare/v3.0.1...v3.1.0
[3.0.1]: https://github.com/marhali/easy-i18n/compare/v3.0.0...v3.0.1
[3.0.0]: https://github.com/marhali/easy-i18n/compare/v2.0.0...v3.0.0
[2.0.0]: https://github.com/marhali/easy-i18n/compare/v1.7.1...v2.0.0
[1.7.1]: https://github.com/marhali/easy-i18n/compare/v1.7.0...v1.7.1
[1.7.0]: https://github.com/marhali/easy-i18n/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/marhali/easy-i18n/compare/v1.5.1...v1.6.0
[1.5.1]: https://github.com/marhali/easy-i18n/compare/v1.5.0...v1.5.1
[1.5.0]: https://github.com/marhali/easy-i18n/compare/v1.4.1...v1.5.0
[1.4.1]: https://github.com/marhali/easy-i18n/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/marhali/easy-i18n/compare/v1.3.0...v1.4.0
[1.3.0]: https://github.com/marhali/easy-i18n/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/marhali/easy-i18n/compare/v1.1.1...v1.2.0
[1.1.1]: https://github.com/marhali/easy-i18n/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/marhali/easy-i18n/compare/v1.0.1...v1.1.0
[1.0.1]: https://github.com/marhali/easy-i18n/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/marhali/easy-i18n/commits/v1.0.0

View File

@ -1,80 +1,71 @@
import org.jetbrains.changelog.Changelog
import org.jetbrains.changelog.markdownToHTML
fun properties(key: String) = providers.gradleProperty(key)
fun environment(key: String) = providers.environmentVariable(key)
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
plugins {
id("java") // Java support
alias(libs.plugins.kotlin) // Kotlin support
alias(libs.plugins.gradleIntelliJPlugin) // Gradle IntelliJ Plugin
alias(libs.plugins.intelliJPlatform) // IntelliJ Platform Gradle Plugin
alias(libs.plugins.changelog) // Gradle Changelog Plugin
alias(libs.plugins.qodana) // Gradle Qodana Plugin
alias(libs.plugins.kover) // Gradle Kover Plugin
}
group = properties("pluginGroup").get()
version = properties("pluginVersion").get()
group = providers.gradleProperty("pluginGroup").get()
version = providers.gradleProperty("pluginVersion").get()
// Set the JVM language level used to build the project.
kotlin {
jvmToolchain(21)
}
// Configure project's dependencies
repositories {
mavenCentral()
// IntelliJ Platform Gradle Plugin Repositories Extension - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-repositories-extension.html
intellijPlatform {
defaultRepositories()
}
}
// Dependencies are managed with Gradle version catalog - read more: https://docs.gradle.org/current/userguide/platforms.html#sub:version-catalog
dependencies {
// implementation(libs.annotations)
implementation(libs.json5.java)
implementation(libs.commons.lang3)
implementation(libs.commons.text)
}
testImplementation(libs.junit)
testImplementation(libs.opentest4j)
// Set the JVM language level used to build the project.
kotlin {
jvmToolchain(17)
}
// IntelliJ Platform Gradle Plugin Dependencies Extension - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html
intellijPlatform {
create(providers.gradleProperty("platformType"), providers.gradleProperty("platformVersion"))
// Configure Gradle IntelliJ Plugin - read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html
intellij {
pluginName = properties("pluginName")
version = properties("platformVersion")
type = properties("platformType")
// Plugin Dependencies. Uses `platformBundledPlugins` property from the gradle.properties file for bundled IntelliJ Platform plugins.
bundledPlugins(providers.gradleProperty("platformBundledPlugins").map { it.split(',') })
// Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file.
plugins = properties("platformPlugins").map { it.split(',').map(String::trim).filter(String::isNotEmpty) }
}
// Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file for plugin from JetBrains Marketplace.
plugins(providers.gradleProperty("platformPlugins").map { it.split(',') })
// Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin
changelog {
groups.empty()
repositoryUrl = properties("pluginRepositoryUrl")
}
// Module Dependencies. Uses `platformBundledModules` property from the gradle.properties file for bundled IntelliJ Platform modules.
bundledModules(providers.gradleProperty("platformBundledModules").map { it.split(',') })
// Configure Gradle Kover Plugin - read more: https://github.com/Kotlin/kotlinx-kover#configuration
koverReport {
defaults {
xml {
onCheck = true
}
testFramework(TestFrameworkType.Platform)
}
}
tasks {
wrapper {
gradleVersion = properties("gradleVersion").get()
}
patchPluginXml {
version = properties("pluginVersion")
sinceBuild = properties("pluginSinceBuild")
untilBuild = properties("pluginUntilBuild")
// Configure IntelliJ Platform Gradle Plugin - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-extension.html
intellijPlatform {
pluginConfiguration {
name = providers.gradleProperty("pluginName")
version = providers.gradleProperty("pluginVersion")
// Extract the <!-- Plugin description --> section from README.md and provide for the plugin's manifest
pluginDescription = providers.fileContents(layout.projectDirectory.file("README.md")).asText.map {
description = providers.fileContents(layout.projectDirectory.file("README.md")).asText.map {
val start = "<!-- Plugin description -->"
val end = "<!-- Plugin description end -->"
with (it.lines()) {
with(it.lines()) {
if (!containsAll(listOf(start, end))) {
throw GradleException("Plugin description section not found in README.md:\n$start ... $end")
}
@ -84,7 +75,7 @@ tasks {
val changelog = project.changelog // local variable for configuration cache compatibility
// Get the latest available change notes from the changelog file
changeNotes = properties("pluginVersion").map { pluginVersion ->
changeNotes = providers.gradleProperty("pluginVersion").map { pluginVersion ->
with(changelog) {
renderItem(
(getOrNull(pluginVersion) ?: getUnreleased())
@ -94,29 +85,77 @@ tasks {
)
}
}
ideaVersion {
sinceBuild = providers.gradleProperty("pluginSinceBuild")
}
}
// Configure UI tests plugin
// Read more: https://github.com/JetBrains/intellij-ui-test-robot
runIdeForUiTests {
systemProperty("robot-server.port", "8082")
systemProperty("ide.mac.message.dialogs.as.sheets", "false")
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
systemProperty("jb.consents.confirmation.enabled", "false")
signing {
certificateChain = providers.environmentVariable("CERTIFICATE_CHAIN")
privateKey = providers.environmentVariable("PRIVATE_KEY")
password = providers.environmentVariable("PRIVATE_KEY_PASSWORD")
}
signPlugin {
certificateChain = environment("CERTIFICATE_CHAIN")
privateKey = environment("PRIVATE_KEY")
password = environment("PRIVATE_KEY_PASSWORD")
}
publishPlugin {
dependsOn("patchChangelog")
token = environment("PUBLISH_TOKEN")
publishing {
token = providers.environmentVariable("PUBLISH_TOKEN")
// The pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3
// Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more:
// https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel
channels = properties("pluginVersion").map { listOf(it.substringAfter('-', "").substringBefore('.').ifEmpty { "default" }) }
channels = providers.gradleProperty("pluginVersion").map { listOf(it.substringAfter('-', "").substringBefore('.').ifEmpty { "default" }) }
}
pluginVerification {
ides {
recommended()
}
}
}
// Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin
changelog {
groups.empty()
repositoryUrl = providers.gradleProperty("pluginRepositoryUrl")
}
// Configure Gradle Kover Plugin - read more: https://github.com/Kotlin/kotlinx-kover#configuration
kover {
reports {
total {
xml {
onCheck = true
}
}
}
}
tasks {
wrapper {
gradleVersion = providers.gradleProperty("gradleVersion").get()
}
publishPlugin {
dependsOn(patchChangelog)
}
}
intellijPlatformTesting {
runIde {
register("runIdeForUiTests") {
task {
jvmArgumentProviders += CommandLineArgumentProvider {
listOf(
"-Drobot-server.port=8082",
"-Dide.mac.message.dialogs.as.sheets=false",
"-Djb.privacy.policy.text=<!--999.999-->",
"-Djb.consents.confirmation.enabled=false",
)
}
}
plugins {
robotServerPlugin()
}
}
}
}

10
codecov.yml Normal file
View File

@ -0,0 +1,10 @@
coverage:
status:
project:
default:
informational: true
threshold: 0%
base: auto
patch:
default:
informational: true

View File

@ -4,22 +4,25 @@ pluginGroup = de.marhali.easyi18n
pluginName = easy-i18n
pluginRepositoryUrl = https://github.com/marhali/easy-i18n
# SemVer format -> https://semver.org
pluginVersion = 4.5.1
pluginVersion = 4.9.0
# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 223
pluginUntilBuild = 241.*
pluginSinceBuild = 242
# IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension
platformType = IU
platformVersion = 2022.3.3
platformVersion = 2025.1.5
# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
platformPlugins = org.jetbrains.kotlin, JavaScript, com.jetbrains.php:223.8836.42
# Example: platformPlugins = com.jetbrains.php:203.4449.22, org.intellij.scala:2023.3.27@EAP
platformPlugins = com.jetbrains.php:242.23726.16
# Example: platformBundledPlugins = com.intellij.java
platformBundledPlugins = org.jetbrains.kotlin, JavaScript
# Example: platformBundledModules = intellij.spellchecker
platformBundledModules =
# Gradle Releases -> https://github.com/gradle/gradle/releases
gradleVersion = 8.7
gradleVersion = 9.0.0
# Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib
kotlin.stdlib.default.dependency = false

View File

@ -1,26 +1,28 @@
[versions]
# libraries
annotations = "24.1.0"
json5-java = "2.0.0"
commons-lang3 = "3.14.0"
commons-text = "1.11.0"
commons-lang3 = "3.18.0"
commons-text = "1.14.0"
junit = "4.13.2"
opentest4j = "1.3.0"
# plugins
kotlin = "1.9.23"
changelog = "2.2.0"
gradleIntelliJPlugin = "1.17.2"
qodana = "2023.3.1"
kover = "0.7.6"
changelog = "2.4.0"
intelliJPlatform = "2.7.2"
kotlin = "2.2.0"
kover = "0.9.1"
qodana = "2025.1.1"
[libraries]
annotations = { group = "org.jetbrains", name = "annotations", version.ref = "annotations" }
json5-java = { group = "de.marhali", name = "json5-java", version.ref = "json5-java" }
commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version.ref = "commons-lang3" }
commons-text = { group = "org.apache.commons", name = "commons-text", version.ref = "commons-text" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
opentest4j = { group = "org.opentest4j", name = "opentest4j", version.ref = "opentest4j" }
[plugins]
changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" }
gradleIntelliJPlugin = { id = "org.jetbrains.intellij", version.ref = "gradleIntelliJPlugin" }
intelliJPlatform = { id = "org.jetbrains.intellij.platform", version.ref = "intelliJPlatform" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" }

Binary file not shown.

View File

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
zipStorePath=wrapper/dists

14
gradlew vendored
View File

@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -112,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@ -203,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

6
gradlew.bat vendored
View File

@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@ -68,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

@ -1,9 +1,9 @@
# Qodana configuration:
# https://www.jetbrains.com/help/qodana/qodana-yaml.html
version: 1.0
linter: jetbrains/qodana-jvm-community:latest
projectJDK: "17"
version: "1.0"
linter: jetbrains/qodana-jvm-community:2024.3
projectJDK: "21"
profile:
name: qodana.recommended
exclude:

View File

@ -1,5 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}
rootProject.name = "easy-i18n"
rootProject.name = "easy-i18n"
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}

View File

@ -10,6 +10,7 @@ import com.intellij.openapi.project.Project;
import de.marhali.easyi18n.dialog.AddDialog;
import de.marhali.easyi18n.model.KeyPath;
import de.marhali.easyi18n.settings.presets.NamingConvention;
import de.marhali.easyi18n.settings.ProjectSettingsService;
import de.marhali.easyi18n.util.DocumentUtil;
@ -47,7 +48,7 @@ class LocalizeItAction extends AnAction {
throw new RuntimeException("Project is null!");
}
AddDialog dialog = new AddDialog(project, new KeyPath(text), text, (key) -> replaceSelectedText(project, editor, key));
AddDialog dialog = new AddDialog(project, new KeyPath(convertKeyToNamingCase(text, project)), text, (key) -> replaceSelectedText(project, editor, key));
dialog.showAndHandle();
}
@ -55,8 +56,8 @@ class LocalizeItAction extends AnAction {
* Replaces the selected text in the editor with a new text generated from the provided key.
*
* @param project the project where the editor belongs
* @param editor the editor where the text is selected
* @param key the key used to generate the replacement text
* @param editor the editor where the text is selected
* @param key the key used to generate the replacement text
*/
private void replaceSelectedText(Project project, @NotNull Editor editor, @NotNull String key) {
int selectionStart = editor.getSelectionModel().getSelectionStart();
@ -71,13 +72,24 @@ class LocalizeItAction extends AnAction {
* Builds a replacement string based on the provided flavor template, key, and document util.
*
* @param flavorTemplate the flavor template string
* @param key the key used to generate the replacement text
* @param documentUtil the document util object used to determine the document type
* @param key the key used to generate the replacement text
* @param documentUtil the document util object used to determine the document type
* @return the built replacement string
*/
private String buildReplacement(String flavorTemplate, String key, DocumentUtil documentUtil) {
if (documentUtil.isVue() || documentUtil.isJsOrTs()) return flavorTemplate + "('" + key + "')";
return flavorTemplate + "(\"" + key + "\")";
}
/**
* Converts a given key to the specified naming convention.
*
* @param key the key to convert
* @param project the project where the key is being converted
* @return the converted key
*/
private String convertKeyToNamingCase(String key, Project project) {
return NamingConvention.convertKeyToConvention(key, ProjectSettingsService.get(project).getState().getCaseFormat());
}
}

View File

@ -0,0 +1,17 @@
package de.marhali.easyi18n.assistance.completion;
import com.intellij.codeInsight.completion.CompletionContributor;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.impl.source.xml.XmlAttributeValueImpl;
/**
* Xml specific completion contributor.
* @author adeptius
*/
public class XmlCompletionContributor extends CompletionContributor {
public XmlCompletionContributor() {
extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(XmlAttributeValueImpl.class),
new KeyCompletionProvider());
}
}

View File

@ -49,7 +49,7 @@ abstract class AbstractFoldingBuilder extends FoldingBuilderEx implements Option
public FoldingDescriptor @NotNull [] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) {
if(quick || !isAssistance(root.getProject())) {
return FoldingDescriptor.EMPTY;
return FoldingDescriptor.EMPTY_ARRAY;
}
List<FoldingDescriptor> descriptors = new ArrayList<>();

View File

@ -0,0 +1,27 @@
package de.marhali.easyi18n.assistance.intention;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.xml.XmlAttributeValue;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Xml specific translation key intention.
* @author adeptius
*/
public class XmlTranslationIntention extends AbstractTranslationIntention {
@Override
protected @Nullable String extractText(@NotNull PsiElement element) {
if(!(element.getParent() instanceof XmlAttributeValue)) {
return null;
}
return ((XmlAttributeValue) element.getParent()).getValue();
}
@Override
@NotNull TextRange convertRange(@NotNull TextRange input) {
return new TextRange(input.getStartOffset() + 1, input.getEndOffset() - 1);
}
}

View File

@ -0,0 +1,38 @@
package de.marhali.easyi18n.assistance.reference;
import com.intellij.openapi.project.Project;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceProvider;
import com.intellij.psi.PsiReferenceRegistrar;
import com.intellij.psi.impl.source.xml.XmlAttributeValueImpl;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
/**
* Xml specific key reference binding
* @author adeptius
*/
public class XmlKeyReferenceContributor extends AbstractKeyReferenceContributor {
@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
registrar.registerReferenceProvider(
PlatformPatterns.psiElement(XmlAttributeValueImpl.class),
getProvider());
}
private PsiReferenceProvider getProvider() {
return new PsiReferenceProvider() {
@Override
public PsiReference @NotNull [] getReferencesByElement(
@NotNull PsiElement element, @NotNull ProcessingContext context) {
Project project = element.getProject();
XmlAttributeValueImpl literalExpression = (XmlAttributeValueImpl) element;
String value = literalExpression.getValue();
return getReferences(project, element, value);
}
};
}
}

View File

@ -3,6 +3,7 @@ package de.marhali.easyi18n.dialog;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.JBTextArea;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.Consumer;
import com.intellij.util.ui.FormBuilder;
@ -38,7 +39,7 @@ abstract class TranslationDialog extends DialogWrapper {
protected final @NotNull Translation origin;
protected final JTextField keyField;
protected final Map<String, JTextField> localeValueFields;
protected final Map<String, JTextArea> localeValueFields;
private final Set<Consumer<TranslationUpdate>> callbacks;
@ -64,7 +65,12 @@ abstract class TranslationDialog extends DialogWrapper {
this.localeValueFields = new HashMap<>();
for(String locale : InstanceManager.get(project).store().getData().getLocales()) {
localeValueFields.put(locale, new JBTextField(value != null ? value.get(locale) : null));
var field = new JBTextArea(value != null ? value.get(locale) : null, 1, 1);
field.setLineWrap(true);
field.setWrapStyleWord(true);
field.setBorder(BorderFactory.createTitledBorder(locale));
localeValueFields.put(locale, field);
}
}
@ -114,7 +120,7 @@ abstract class TranslationDialog extends DialogWrapper {
TranslationValue value = new TranslationValue();
for(Map.Entry<String, JTextField> entry : localeValueFields.entrySet()) {
for(Map.Entry<String, JTextArea> entry : localeValueFields.entrySet()) {
value.put(entry.getKey(), entry.getValue().getText());
}
@ -136,8 +142,8 @@ abstract class TranslationDialog extends DialogWrapper {
private JComponent createLocalesPanel() {
FormBuilder builder = FormBuilder.createFormBuilder();
for(Map.Entry<String, JTextField> localeEntry : localeValueFields.entrySet()) {
builder.addLabeledComponent(localeEntry.getKey(), localeEntry.getValue(), 6, true);
for(Map.Entry<String, JTextArea> localeEntry : localeValueFields.entrySet()) {
builder.addComponent(localeEntry.getValue(), 6);
}
JScrollPane scrollPane = new JBScrollPane(builder.getPanel());

View File

@ -3,35 +3,61 @@ package de.marhali.easyi18n.settings;
import de.marhali.easyi18n.io.parser.ParserStrategyType;
import de.marhali.easyi18n.io.folder.FolderStrategyType;
import de.marhali.easyi18n.settings.presets.NamingConvention;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* API to access the project-specific configuration for this plugin.
*
* @author marhaliu
*/
public interface ProjectSettings {
// Resource Configuration
@Nullable String getLocalesDirectory();
@NotNull FolderStrategyType getFolderStrategy();
@NotNull ParserStrategyType getParserStrategy();
@NotNull String getFilePattern();
@Nullable
String getLocalesDirectory();
@NotNull
FolderStrategyType getFolderStrategy();
@NotNull
ParserStrategyType getParserStrategy();
@NotNull
String getFilePattern();
boolean isIncludeSubDirs();
boolean isSorting();
// Editor Configuration
@Nullable String getNamespaceDelimiter();
@NotNull String getSectionDelimiter();
@Nullable String getContextDelimiter();
@Nullable String getPluralDelimiter();
@Nullable String getDefaultNamespace();
@NotNull String getPreviewLocale();
@Nullable
String getNamespaceDelimiter();
@NotNull
String getSectionDelimiter();
@Nullable
String getContextDelimiter();
@Nullable
String getPluralDelimiter();
@Nullable
String getDefaultNamespace();
@NotNull
String getPreviewLocale();
boolean isNestedKeys();
boolean isAssistance();
// Experimental Configuration
boolean isAlwaysFold();
String getFlavorTemplate();
@NotNull
NamingConvention getCaseFormat();
}

View File

@ -13,6 +13,7 @@ import com.intellij.util.ui.FormBuilder;
import de.marhali.easyi18n.io.parser.ArrayMapper;
import de.marhali.easyi18n.io.parser.ParserStrategyType;
import de.marhali.easyi18n.settings.presets.NamingConvention;
import de.marhali.easyi18n.settings.presets.Preset;
import javax.swing.*;
@ -26,6 +27,7 @@ import java.util.ResourceBundle;
/**
* Configuration panel with all possible options for this plugin.
*
* @author marhali
*/
public class ProjectSettingsComponent extends ProjectSettingsComponentState {
@ -64,7 +66,9 @@ public class ProjectSettingsComponent extends ProjectSettingsComponentState {
.addVerticalGap(24)
.addComponent(new TitledSeparator(bundle.getString("settings.experimental.title")))
.addComponent(constructAlwaysFoldField())
.addVerticalGap(12)
.addLabeledComponent(bundle.getString("settings.experimental.flavor-template"), constructFlavorTemplate(), 1, false)
.addLabeledComponent(bundle.getString("settings.experimental.key-naming-format.title"), constructKeyCaseFormater(), 1, false)
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}
@ -226,6 +230,14 @@ public class ProjectSettingsComponent extends ProjectSettingsComponentState {
return flavorTemplate;
}
private JComponent constructKeyCaseFormater() {
KeyCaseFormater = new ComboBox<>(NamingConvention.getEnumNames());
KeyCaseFormater.setToolTipText(bundle.getString("settings.experimental.key-naming-format.tooltip"));
KeyCaseFormater.setMinimumAndPreferredWidth(200);
return KeyCaseFormater;
}
private ItemListener handleParserChange() {
return e -> {
if (e.getStateChange() == ItemEvent.SELECTED) {

View File

@ -5,12 +5,14 @@ import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import de.marhali.easyi18n.io.parser.ParserStrategyType;
import de.marhali.easyi18n.io.folder.FolderStrategyType;
import de.marhali.easyi18n.settings.presets.NamingConvention;
import de.marhali.easyi18n.settings.presets.Preset;
import javax.swing.*;
/**
* Mandatory for state management for the project settings component.
*
* @author marhali
*/
public class ProjectSettingsComponentState {
@ -41,6 +43,7 @@ public class ProjectSettingsComponentState {
protected JCheckBox alwaysFold;
protected JTextField flavorTemplate;
protected ComboBox<String> KeyCaseFormater;
protected ProjectSettingsState getState() {
// Every field needs to provide its state
@ -65,8 +68,11 @@ public class ProjectSettingsComponentState {
state.setAssistance(assistance.isSelected());
state.setAlwaysFold(alwaysFold.isSelected());
state.setFlavorTemplate(flavorTemplate.getText());
state.setCaseFormat(NamingConvention.fromString(KeyCaseFormater.getSelectedItem().toString()));
return state;
}
@ -92,5 +98,7 @@ public class ProjectSettingsComponentState {
alwaysFold.setSelected(state.isAlwaysFold());
flavorTemplate.setText(state.getFlavorTemplate());
KeyCaseFormater.setSelectedItem(state.getCaseFormat().getName());
}
}

View File

@ -6,6 +6,7 @@ import de.marhali.easyi18n.io.parser.ParserStrategyType;
import de.marhali.easyi18n.io.folder.FolderStrategyType;
import de.marhali.easyi18n.settings.presets.DefaultPreset;
import de.marhali.easyi18n.settings.presets.NamingConvention;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -13,32 +14,48 @@ import java.util.Objects;
/**
* Represents the project-specific configuration of this plugin.
*
* @author marhali
*/
public class ProjectSettingsState implements ProjectSettings {
// Resource Configuration
@Property private String localesDirectory;
@Property private FolderStrategyType folderStrategy;
@Property private ParserStrategyType parserStrategy;
@Property private String filePattern;
@Property
private String localesDirectory;
@Property
private FolderStrategyType folderStrategy;
@Property
private ParserStrategyType parserStrategy;
@Property
private String filePattern;
@Property private Boolean includeSubDirs;
@Property private boolean sorting;
@Property
private Boolean includeSubDirs;
@Property
private boolean sorting;
// Editor configuration
@Property private String namespaceDelimiter;
@Property private String sectionDelimiter;
@Property private String contextDelimiter;
@Property private String pluralDelimiter;
@Property private String defaultNamespace;
@Property private String previewLocale;
@Property
private String namespaceDelimiter;
@Property
private String sectionDelimiter;
@Property
private String contextDelimiter;
@Property
private String pluralDelimiter;
@Property
private String defaultNamespace;
@Property
private String previewLocale;
@Property private Boolean nestedKeys;
@Property private Boolean assistance;
@Property
private Boolean nestedKeys;
@Property
private Boolean assistance;
// Experimental configuration
@Property private Boolean alwaysFold;
@Property
private Boolean alwaysFold;
/**
* The `flavorTemplate` specifies the format used for replacing strings with their i18n (internationalization) counterparts.
@ -47,7 +64,11 @@ public class ProjectSettingsState implements ProjectSettings {
* the specific framework or developers' preferences for handling i18n. The ability to dynamically change this template adds flexibility and customization
* to cater to different i18n handling methods.
*/
@Property private String flavorTemplate;
@Property
private String flavorTemplate;
@Property
private NamingConvention caseFormat;
public ProjectSettingsState() {
this(new DefaultPreset());
@ -75,6 +96,7 @@ public class ProjectSettingsState implements ProjectSettings {
this.alwaysFold = defaults.isAlwaysFold();
this.flavorTemplate = defaults.getFlavorTemplate();
this.caseFormat = defaults.getCaseFormat();
}
@Override
@ -158,6 +180,11 @@ public class ProjectSettingsState implements ProjectSettings {
return this.flavorTemplate;
}
@Override
public @NotNull NamingConvention getCaseFormat() {
return this.caseFormat;
}
public void setLocalesDirectory(String localesDirectory) {
this.localesDirectory = localesDirectory;
}
@ -218,10 +245,15 @@ public class ProjectSettingsState implements ProjectSettings {
this.alwaysFold = alwaysFold;
}
public void setFlavorTemplate(String flavorTemplate){
public void setFlavorTemplate(String flavorTemplate) {
this.flavorTemplate = flavorTemplate;
}
public void setCaseFormat(NamingConvention caseFormat) {
this.caseFormat = caseFormat;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -242,7 +274,8 @@ public class ProjectSettingsState implements ProjectSettings {
&& Objects.equals(nestedKeys, that.nestedKeys)
&& Objects.equals(assistance, that.assistance)
&& Objects.equals(alwaysFold, that.alwaysFold)
&& Objects.equals(flavorTemplate,that.flavorTemplate);
&& Objects.equals(flavorTemplate, that.flavorTemplate)
&& Objects.equals(caseFormat, that.caseFormat);
}
@Override
@ -250,7 +283,7 @@ public class ProjectSettingsState implements ProjectSettings {
return Objects.hash(
localesDirectory, folderStrategy, parserStrategy, filePattern, includeSubDirs,
sorting, namespaceDelimiter, sectionDelimiter, contextDelimiter, pluralDelimiter,
defaultNamespace, previewLocale, nestedKeys, assistance, alwaysFold,flavorTemplate
defaultNamespace, previewLocale, nestedKeys, assistance, alwaysFold, flavorTemplate, caseFormat
);
}
@ -273,6 +306,7 @@ public class ProjectSettingsState implements ProjectSettings {
", assistance=" + assistance +
", alwaysFold=" + alwaysFold +
", flavorTemplate=" + flavorTemplate +
", caseFormat=" + caseFormat.toString() +
'}';
}
}

View File

@ -91,4 +91,9 @@ public class DefaultPreset implements ProjectSettings {
public String getFlavorTemplate() {
return "$i18n.t";
}
@Override
public @NotNull NamingConvention getCaseFormat() {
return NamingConvention.CAMEL_CASE;
}
}

View File

@ -0,0 +1,92 @@
package de.marhali.easyi18n.settings.presets;
import com.google.common.base.CaseFormat;
import java.util.Arrays;
/**
* Enum representing different naming conventions.
* Provides utility methods to convert keys to the specified convention.
*/
public enum NamingConvention {
CAMEL_CASE("Camel Case"),
PASCAL_CASE("Pascal Case"),
SNAKE_CASE("Snake Case"),
SNAKE_CASE_UPPERCASE("Snake Case (Uppercase)");
private final String name;
private NamingConvention(String name) {
this.name = name;
}
/**
* Retrieves the name of the current instance of the class.
*
* @return the name of the current instance
*/
public String getName() {
return this.name;
}
@Override
public String toString() {
return super.name().toLowerCase();
}
/**
* Converts a string representation of a naming convention to the corresponding NamingConvention enum value.
*
* @param name the string representation of the naming convention
* @return the corresponding NamingConvention enum value
*/
static public NamingConvention fromString(String name) {
for (NamingConvention value : NamingConvention.values()) {
if (value.getName().equals(name))
return value;
}
return NamingConvention.CAMEL_CASE;
}
/**
* Returns an array of strings representing the names of the enum values in the {@link NamingConvention} enum.
*
* @return an array of strings representing the enum names
*/
static public String[] getEnumNames() {
return Arrays.stream(NamingConvention.values())
.map(NamingConvention::getName)
.toArray(String[]::new);
}
/**
* Converts a given key to the specified naming convention.
*
* @param key the key to convert
* @param convention the naming convention to convert the key to
* @return the converted key
*/
static public String convertKeyToConvention(String key, NamingConvention convention) {
String newKey = key.toLowerCase();
newKey = newKey.replaceAll("\\s+", "_");
return switch (convention) {
case SNAKE_CASE:
yield formatToSnakeCase(newKey, false);
case SNAKE_CASE_UPPERCASE:
yield formatToSnakeCase(newKey, true);
case CAMEL_CASE:
yield formatToCamelCase(newKey, false);
case PASCAL_CASE:
yield formatToCamelCase(newKey, true);
};
}
static private String formatToCamelCase(String key, boolean capitalized) {
return CaseFormat.LOWER_UNDERSCORE.to(capitalized ? CaseFormat.UPPER_CAMEL : CaseFormat.LOWER_CAMEL, key);
}
static private String formatToSnakeCase(String key, boolean capitalized) {
return CaseFormat.LOWER_UNDERSCORE.to(capitalized ? CaseFormat.UPPER_UNDERSCORE : CaseFormat.LOWER_UNDERSCORE, key);
}
}

View File

@ -91,4 +91,8 @@ public class ReactI18NextPreset implements ProjectSettings {
public String getFlavorTemplate() {
return "$i18n.t";
}
@Override
public @NotNull NamingConvention getCaseFormat() {
return NamingConvention.CAMEL_CASE;
}
}

View File

@ -90,4 +90,9 @@ public class VueI18nPreset implements ProjectSettings {
public String getFlavorTemplate() {
return "$i18n.t";
}
@Override
public @NotNull NamingConvention getCaseFormat() {
return NamingConvention.CAMEL_CASE;
}
}

View File

@ -1,5 +1,11 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<psi.referenceContributor
language="HTML"
implementation="de.marhali.easyi18n.assistance.reference.XmlKeyReferenceContributor"
/>
<lang.foldingBuilder
language="HTML"
implementationClass="de.marhali.easyi18n.assistance.folding.XmlFoldingBuilder"
@ -9,5 +15,14 @@
language="HTML"
implementationClass="de.marhali.easyi18n.assistance.documentation.CommonDocumentationProvider"
/>
<completion.contributor
language="HTML"
implementationClass="de.marhali.easyi18n.assistance.completion.XmlCompletionContributor"
/>
<intentionAction>
<className>de.marhali.easyi18n.assistance.intention.XmlTranslationIntention</className>
</intentionAction>
</extensions>
</idea-plugin>

View File

@ -62,6 +62,9 @@ settings.experimental.always-fold.title=Always fold translation keys
settings.experimental.always-fold.tooltip=Forces the editor to always display the value behind a translation key. The value cannot be unfolded when this function is active.
settings.experimental.flavor-template =I18n flavor template
settings.experimental.flavor-template-tooltip = Specify how to replace strings with i18n representation.
settings.experimental.key-naming-format.title=Key format of extracted translation
settings.experimental.key-naming-format.tooltip=Choose Naming Convention for the keys of extracted translation
error.io=An error occurred while processing translation files. \n\
Config: {0} => {1} ({2}) \n\
Path: {3} \n\

View File

@ -3,6 +3,7 @@ package de.marhali.easyi18n;
import de.marhali.easyi18n.io.parser.ParserStrategyType;
import de.marhali.easyi18n.io.folder.FolderStrategyType;
import de.marhali.easyi18n.model.KeyPath;
import de.marhali.easyi18n.settings.presets.NamingConvention;
import de.marhali.easyi18n.settings.ProjectSettings;
import de.marhali.easyi18n.util.KeyPathConverter;
@ -172,6 +173,11 @@ public class KeyPathConverterTest {
public boolean isIncludeSubDirs() {
return false;
}
@Override
public @NotNull NamingConvention getCaseFormat() {
return NamingConvention.CAMEL_CASE;
}
});
}
}

View File

@ -22,6 +22,7 @@ import java.util.Objects;
/**
* End-to-end test case.
*
* @author marhali
*/
public abstract class EndToEndTestCase extends BasePlatformTestCase {
@ -57,7 +58,8 @@ public abstract class EndToEndTestCase extends BasePlatformTestCase {
out.setLocalesDirectory(tempPath.toString());
ProjectSettingsService.get(getProject()).setState(out);
InstanceManager.get(getProject()).store().saveToPersistenceLayer(success -> {});
InstanceManager.get(getProject()).store().saveToPersistenceLayer(success -> {
});
// Compare file structure and contents
IOFileFilter fileFilter = TrueFileFilter.INSTANCE;
@ -73,7 +75,7 @@ public abstract class EndToEndTestCase extends BasePlatformTestCase {
assertEquals(originalFiles.length, outputFiles.length);
for(int i = 0; i < originalFiles.length; i++) {
for (int i = 0; i < originalFiles.length; i++) {
File originalFile = originalFiles[i];
File outputFile = outputFiles[i];
@ -82,4 +84,4 @@ public abstract class EndToEndTestCase extends BasePlatformTestCase {
FileUtils.readFileToString(outputFile, CHARSET));
}
}
}
}

View File

@ -7,6 +7,7 @@ import de.marhali.easyi18n.io.parser.properties.PropertiesMapper;
import de.marhali.easyi18n.io.parser.properties.SortableProperties;
import de.marhali.easyi18n.model.TranslationData;
import de.marhali.easyi18n.model.KeyPath;
import de.marhali.easyi18n.settings.presets.NamingConvention;
import de.marhali.easyi18n.settings.ProjectSettings;
import de.marhali.easyi18n.util.KeyPathConverter;
@ -19,6 +20,7 @@ import java.util.*;
/**
* Unit tests for {@link PropertiesMapper}.
*
* @author marhali
*/
public class PropertiesMapperTest extends AbstractMapperTest {
@ -245,6 +247,11 @@ public class PropertiesMapperTest extends AbstractMapperTest {
public boolean isIncludeSubDirs() {
return false;
}
@Override
public @NotNull NamingConvention getCaseFormat() {
return NamingConvention.CAMEL_CASE;
}
});
}
}

View File

@ -0,0 +1,35 @@
package de.marhali.easyi18n.settings;
import com.intellij.testFramework.fixtures.BasePlatformTestCase;
import de.marhali.easyi18n.settings.presets.NamingConvention;
import java.io.IOException;
public class NamingConventionTest extends BasePlatformTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
}
public void testConvertToNamingConvention() throws IOException {
assertEquals("helloWorld", NamingConvention.convertKeyToConvention("Hello World", NamingConvention.CAMEL_CASE));
assertEquals("hello_world", NamingConvention.convertKeyToConvention("Hello World", NamingConvention.SNAKE_CASE));
assertEquals("HelloWorld", NamingConvention.convertKeyToConvention("Hello World", NamingConvention.PASCAL_CASE));
assertEquals("HELLO_WORLD", NamingConvention.convertKeyToConvention("Hello World", NamingConvention.SNAKE_CASE_UPPERCASE));
}
public void testGetEnumNames() throws Exception {
String[] expected = {"Camel Case", "Pascal Case", "Snake Case", "Snake Case (Uppercase)"};
String[] actual = NamingConvention.getEnumNames();
assertEquals(expected.length, actual.length);
}
public void testFromString() {
assertEquals(NamingConvention.CAMEL_CASE, NamingConvention.fromString("Camel Case"));
assertEquals(NamingConvention.PASCAL_CASE, NamingConvention.fromString("Pascal Case"));
assertEquals(NamingConvention.SNAKE_CASE, NamingConvention.fromString("Snake Case"));
assertEquals(NamingConvention.SNAKE_CASE_UPPERCASE, NamingConvention.fromString("Snake Case (Uppercase)"));
assertEquals(NamingConvention.CAMEL_CASE, NamingConvention.fromString("Invalid Input"));
}
}

View File

@ -4,9 +4,11 @@ import com.intellij.testFramework.fixtures.BasePlatformTestCase;
import com.intellij.util.xmlb.XmlSerializerUtil;
import de.marhali.easyi18n.settings.presets.DefaultPreset;
import de.marhali.easyi18n.settings.presets.NamingConvention;
/**
* Tests for the project settings service itself.
*
* @author marhali
*/
public class ProjectSettingsServiceTest extends BasePlatformTestCase {
@ -35,4 +37,12 @@ public class ProjectSettingsServiceTest extends BasePlatformTestCase {
ProjectSettingsState after = XmlSerializerUtil.createCopy(previous);
assertEquals("mySinglePropTest", after.getLocalesDirectory());
}
public void testPersistenceFormatCase() {
ProjectSettingsState previous = new ProjectSettingsState();
assertEquals(previous.getCaseFormat(), NamingConvention.CAMEL_CASE);
previous.setCaseFormat(NamingConvention.SNAKE_CASE);
ProjectSettingsState after = XmlSerializerUtil.createCopy(previous);
assertEquals(after.getCaseFormat(), NamingConvention.SNAKE_CASE);
}
}

View File

@ -3,11 +3,13 @@ package de.marhali.easyi18n.settings;
import de.marhali.easyi18n.io.folder.FolderStrategyType;
import de.marhali.easyi18n.io.parser.ParserStrategyType;
import de.marhali.easyi18n.settings.presets.NamingConvention;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Settings preset to test the functionality of the settings service.
*
* @author marhali
*/
public class SettingsTestPreset implements ProjectSettings {
@ -88,6 +90,11 @@ public class SettingsTestPreset implements ProjectSettings {
@Override
public String getFlavorTemplate() {
return "";
return "t";
}
@Override
public @NotNull NamingConvention getCaseFormat() {
return NamingConvention.CAMEL_CASE;
}
}