Giter VIP home page Giter VIP logo

hyperdevs-team / poeditor-android-gradle-plugin Goto Github PK

View Code? Open in Web Editor NEW
50.0 7.0 26.0 364 KB

Gradle plug-in that enables importing PoEditor localized strings directly to an Android project

License: Apache License 2.0

Kotlin 99.16% Shell 0.84%
poeditor poeditor-string poeditor-api android android-localization android-strings android-poeditor gradle-plugin gradle-kotlin android-translation

poeditor-android-gradle-plugin's Introduction

PoEditor Android Gradle Plug-in

Release

Simple plug-in that eases importing PoEditor localized strings to your Android project.

Purpose

This plug-in super-charges your Android project by providing tasks to download your localized strings from the PoEditor service into you Android project. It also provides a built-in syntax to handle placeholders to enhance the already awesome Android support from PoEditor.

Minimum requirements

  • Gradle 8.2 or above
  • Android Gradle Plug-in 8.0 or above

Setting Up

In your main build.gradle, add jitpack.io repository in the buildscript block and include the plug-in as a dependency:

Groovy
buildscript {
    repositories { 
        maven { url "https://jitpack.io" }
    }
    dependencies {
        classpath "com.github.hyperdevs-team:poeditor-android-gradle-plugin:<latest_version>"
    }
}
Kotlin (using classpath)
buildscript {
    repositories { 
        maven { url = java.net.URI("https://jitpack.io") }
    }
    dependencies {
        classpath("com.github.hyperdevs-team:poeditor-android-gradle-plugin:<latest_version>")
    }
}
Kotlin (using the plugins block)

Top-level settings.gradle.kts

pluginManagement {
    repositories {
        maven { url = java.net.URI("https://jitpack.io") }
    }
    resolutionStrategy {
        eachPlugin {
            // Add custom plugin ID for the PoEditor plugin.
            // This is required because the plugin is not published in the Gradle plugin portal.
            if (requested.id.id == "com.hyperdevs.poeditor") {
                useModule("com.github.hyperdevs-team:poeditor-android-gradle-plugin:${requested.version}")
            }
        }
    }
}

Top-level build.gradle.kts

plugins {
    id("com.hyperdevs.poeditor") version "<latest_version>" apply false
}

How to use

Apply and configure the plug-in in your app's build.gradle file:

Groovy
apply plugin: "com.android.application"
apply plugin: "com.hyperdevs.poeditor"

poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
}
Kotlin
plugins {
    id("com.android.application")
    id("com.hyperdevs.poeditor")
}

poEditor {
    apiToken.set("your_api_token")
    projectId.set(12345)
    defaultLang.set("en")
}

The complete attribute list is the following:

Attribute Description
apiToken PoEditor API Token.
projectId PoEditor project ID.
defaultLang (Optional) The lang to be used to build default strings.xml (/values folder). Defaults to English (en).
defaultResPath (Since 1.3.0) (Optional) Path where the plug-in should dump strings. Defaults to the module's default (or build variant) res path.
enabled (Since 1.4.0) (Optional) Enables the generation of the block's related task. Defaults to true.
tags (Since 2.1.0) (Optional) List of PoEditor tags to download. Defaults to empty list.
languageValuesOverridePathMap (Since 2.2.0) (Optional) Map of language_code:path entries that you want to override the default language values folder with. Defaults to empty map.
minimumTranslationPercentage (Since 2.3.0) (Optional) The minimum accepted percentage of translated strings per language. Languages with fewer translated strings will not be fetched. Defaults to no minimum, allowing all languages to be fetched.
filters (Since 2.4.0) (Optional) List of PoEditor filters to use during download. Defaults to empty list. Accepted values are defined by the POEditor API.
resFileName (Since 3.1.0) (Optional) Sets the file name for the imported string resource XML files. Defaults to strings.
order (Since 3.1.0) (Optional) Defines how to order the export. Accepted values are defined by the POEditor API.
unquoted (Since 3.2.0) (Optional) Defines if the strings should be unquoted, overriding default PoEditor configuration. Defaults to false.
unescapeHtmlTags (Since 3.4.0) (Optional) Whether or not to unescape HTML entitites from strings. Defaults to true.
untranslatableStringsRegex (Since 4.2.0) (Optional) Pattern to use to mark strings as translatable=false in the strings file. Defaults to null.

After the configuration is done, just run the new importPoEditorStrings task via Android Studio or command line:

./gradlew importPoEditorStrings

This task will:

  • Download all strings files (every available lang) from PoEditor given the api token and project id.
  • Process the incoming strings to fix some PoEditor incompatibilities with Android strings system.
  • Create and save strings.xml (or whatever file name you desire by using the resFileName attribute) files to /values-<lang> (or /values in case of the default lang). It supports region specific languages by creating the proper folders (i.e. /values-es-rMX).

Enhanced syntax

The plug-in enhances your PoEditor experience by adding useful features over your project by adding some custom syntax for certain tasks.

Variables

The plug-in does not parse string placeholders, instead it uses variables with a specific markup to use in PoEditor's string definition: it uses a double braces syntax to declare them. This allows more clarity for translators that use the platform, since it allows them to know what the placeholders really mean and better reuse them in translations.

For example, the PoEditor string:

welcome_message: Hey {{user_name}} how are you

will become, in strings.xml

<string name="welcome_message">Hey %1$s how are you</string>

If you need more than one variable in the same string, you can also use ordinals. The string:

welcome_message: Hey {1{user_name}} how are you, today offer is {2{current_offer}}

will become, in strings.xml

<string name="welcome_message">Hey %1$s how are you, today offer is %2$s</string>

This way you can change the order of the placeholders depending on the language:

The same string, with the following Spanish translation:

welcome_message: La oferta del día es {2{current_offer}} para ti, {1{user_name}}

will become, in values-es/strings.xml

<string name="welcome_message">La oferta del día es %2$s para ti, %1$s</string>

Tablet specific strings

You can mark some strings as tablet specific strings by adding _tabletsuffix to the string key in PoEditor. The plug-in will extract tablet strings to its own XML and save it in values-<lang>-sw600dp.

If you define the following string in PoEditor: welcome_message: Hey friend and welcome_message_tablet: Hey friend how are you doing today, you look great!

The plug-in will create two strings.xml files:

/values/strings.xml

<string name="welcome_message">Hey friend</string>

/values-sw600dp/strings.xml

<string name="welcome_message">Hey friend how are you doing today, you look great!</string>

Handling multiple flavors and build types

Sometimes we might want to import different strings for a given flavor (for example, in white label apps, we could have different string definitions depending on the brand where they're used). The plugin supports this kind of apps by providing specific configurations via the poEditorConfig block.

Let's see an example configuration:

Groovy
poEditor {
    // Default config that applies to all flavor/build type configurations. 
    // Also executed when calling 'importPoEditorStrings'
}

android {
    // If you have the following flavors...
    flavorDimensions 'type'
    productFlavors {
        free { dimension 'type' }
        paid { dimension 'type' }
    }

    poEditorConfig {
        free {
            // Configuration for the free flavor, same syntax as the standard 'poEditor' block
        }
        paid {
            // Configuration for the paid flavor, same syntax as the standard 'poEditor' block
        }
        debug {
            // Configuration for the debug build type, same syntax as the standard 'poEditor' block
        }
        release {
            // Configuration for the release build type, same syntax as the standard 'poEditor' block
        }
    }
}
Kotlin
poEditor {
    // Default config that applies to all flavor/build type configurations. 
    // Also executed when calling 'importPoEditorStrings'
}

android {
    // If you have the following flavors...
    flavorDimensions("type")

    productFlavors {
        register("free") { setDimension("type") }
        register("paid") { setDimension("type") }
    }

    poEditorConfig {
        register("free") {
            // Configuration for the free flavor, same syntax as the standard 'poEditor' block
        }
        register("paid") {
            // Configuration for the paid flavor, same syntax as the standard 'poEditor' block
        }
        register("debug") {
            // Configuration for the debug build type, same syntax as the standard 'poEditor' block
        }
        register("release") {
            // Configuration for the release build type, same syntax as the standard 'poEditor' block
        }
    }
}

Each flavor (free and paid) and build type (debug and release) will have its own task to import strings for said configuration: importFreePoEditorStrings, importPaidPoEditorStrings, importDebugPoEditorStrings and importReleasePoEditorStrings.

Now the importPoEditorStrings task will import the main strings configured in the poEditor block and also the strings for each defined flavor or build type.`

Handling library modules

Requires version 1.3.0 of the plug-in

You can also apply the plug-in to library modules. Here's an example: Apply and configure the plug-in in your library's build.gradle file:

Groovy
apply plugin: "com.android.library"
apply plugin: "com.hyperdevs.poeditor"

poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
}
Kotlin
plugins {
    id "com.android.library"
    id "com.hyperdevs.poeditor"
}

poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
}

You can also apply flavor and build type-specific configurations as you would do when setting them up with application modules. The plug-in will generate the proper tasks needed to import the strings under your module: :<your_module>:import<your_flavor_or_build_type_if_any>PoEditorStrings

Disabling task generation for specific configurations

Requires version 1.4.0 of the plug-in

There may be some cases where you only want certain configurations to have a related task. One of these examples may be to only have tasks for the configured flavors or build types, but you don't want to have the main poEditor block to download any strings. For these cases you have the enabled variable that you can set to false when you want to disable a configuration.

Keep in mind that, if you disable the main poEditor block, you'll need to enable each specific configuration separately since they inherit the main block configuration. Let's see how this works:

Groovy
poEditor {
    // Default config that applies to all flavor/build type configurations. 
    // Also executed when calling 'importPoEditorStrings'
    enabled = false // This'll disable task generation for every configuration.
    apiToken = "your_common_api_token"
}

android {
    flavorDimensions 'type'
    productFlavors {
        free { dimension 'type' }
        paid { dimension 'type' }
    }

    poEditorConfig {
        free {
            // Specific configuration for the free flavor
            enabled = true // Explicitly enabled since the main block disables task generation
            projectId = 12345
        }
        paid {
            // Specific configuration for the paid flavor
            enabled = true // Explicitly enabled since the main block disables task generation
            projectId = 54321
        }
    }
}
Kotlin
poEditor {
    // Default config that applies to all flavor/build type configurations. 
    // Also executed when calling 'importPoEditorStrings'
    enabled = false // This'll disable task generation for every configuration.
    apiToken = "your_common_api_token"
}

android {
    // If you have the following flavors...
    flavorDimensions("type")

    productFlavors {
        register("free") { setDimension("type") }
        register("paid") { setDimension("type") }
    }

    poEditorConfig {
        register("free") {
            // Specific configuration for the free flavor
            enabled = true // Explicitly enabled since the main block disables task generation
            projectId = 12345
        }
        register("paid") {
            // Specific configuration for the paid flavor
            enabled = true // Explicitly enabled since the main block disables task generation
            projectId = 54321
        }
    }
}

Handling tags

Requires version 2.1.0 of the plug-in

You can also select the tags that you want strings to be downloaded from PoEditor, based on the tags that you defined in your PoEditor project.

Groovy
poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
    tags = ["tag1", "tag2"] // Download strings with the specified tags
}
Kotlin
poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
    tags = listOf("tag1", "tag2")
}

Overriding default values folder for specific languages

Requires version 2.2.0 of the plug-in

Sometimes you may want to override the default values path where the plug-in stores the downloaded strings. For example, you may have a project where you have custom languages suited to your app flavors in PoEditor (let's say free and paid). The plug-in would create two folders in your app's res folder (values-free and values-paid) by default; this would not be ideal if you want the string values to their respective flavors.

You can add the parameter languageValuesOverridePathMap to your poEditor or poEditorConfig block to change the path of the values folder where the strings file will be stored for a given language code:

Groovy
poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
    languageValuesOverridePathMap = [
            "free" : "${rootDir}/app/src/free/res/values", 
            "paid" : "${rootDir}/app/src/paid/res/values"
    ]
}
Kotlin
poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
    languageValuesOverridePathMap = mapOf(
        "free" to "${rootDir}/app/src/free/res/values",
        "paid" to "${rootDir}/app/src/paid/res/values"
    )
}

Tweaking minimum translation percentages

Requires version 2.3.0 of the plug-in

The plug-in also allows setting a minimum percentage of translated strings to download languages. This is set-up with the minimumTranslationPercentage parameter in your poEditor or poEditorConfig blocks:

Groovy
poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
    minimumTranslationPercentage = 85
}
Kotlin
poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
    minimumTranslationPercentage = 85
}

Handling filters

Requires version 2.4.0 of the plug-in

The plug-in also allows setting filters for narrowing down the type of terms to be downloaded. Supported filters are defined by the POEditor API and currently include: translated, untranslated, fuzzy, not_fuzzy, automatic, not_automatic, proofread, not_proofread. At the moment it's not possible to set different filters per language.
This is set-up with the optional filters parameter in your poEditor or poEditorConfig blocks:

Groovy
poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
    filters = ["translated", "not_fuzzy"]
}
Kotlin
poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
    filters = listOf("translated", "not_fuzzy")
}

Toggling HTML unescaping

Requires version 3.4.0 of the plug-in

You can enable or disable HTML tags unescaping with the unescapeHtmlTags flag.

Groovy
poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
    unescapeHtmlTags = false
}
Kotlin
poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
    unescapeHtmlTags = false
}

Creating extra PoEditor tasks

Requires version 4.1.0 of the plug-in

You can create extra PoEditor tasks to import strings for other projects, for example. You can do so by adding this to your build.gradle(.kts) file:

Kotlin
tasks.register("importCustomPoEditorStrings", ImportPoEditorStringsTask::class.java) {
    description = "Imports custom strings from POEditor."
    group = "strings"

    apiToken = "another_token_from_a_different_project"
    projectId = 12345
    resFileName = "strings_custom"
}

Mark strings as untranslatable

Requires version 4.2.0 of the plug-in

You can use the untranslatableStringsRegex property to define a regex to mark matching PoEditor string keys as untranslatable. These strings will be marked as translatable="false" in the final strings file.

Groovy
poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
    untranslatableStringsRegex = "(.*)"
}
Kotlin
poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
    untranslatableStringsRegex = "(.*)"
}

Keep in mind that the regex must match the whole string name and not just a part, as it relies on CharSequence.matches(Regex) from the Kotlin API.

iOS alternative

If you want a similar solution for your iOS projects, check this out: poeditor-parser-swift

Authors & contributors

Thanks to all the people who contributed to making the plug-in better!

Acknowledgements

The work in this repository up to April 28th, 2021 was done by bq. Thanks for all the work!!

License

This project is licensed under the Apache Software License, Version 2.0.

   Copyright 2021 HyperDevs
   
   Copyright 2016 BQ

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

poeditor-android-gradle-plugin's People

Contributors

adriangl avatar bogdanzurac avatar danielgallegovico avatar gustavaa avatar imartinez avatar martinakdaniel avatar nodejsjs avatar nokite avatar warrenfaith avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

poeditor-android-gradle-plugin's Issues

Unescape HTML tags being skipped during unescapeHtmlTags()

SW details (please complete the following information):

  • IDE version: [IntelliJ IDEA 2021.2.1 (Community Edition)
    Build #IC-212.5080.55, built on August 24, 2021]
  • Plug-in Version 2.0.0 - 2.4.0

Summary and background of the bug
Having a string like the following in POEditor:

<![CDATA[
  <br />
  <p><a href="mailto:[email protected]">ABC Email</a></p>
]]>

Makes the plugin to crash and never download the strings.

Steps to reproduce
Steps to reproduce the behaviour:

  1. Create a string with the above string in your POEditor project
  2. Try to sycn the string
  3. See error
> org.xml.sax.SAXParseException; lineNumber: 147; columnNumber: 235; The element type "p" must be terminated by the matching end-tag "</p>".

I followed the exception and found that
String.unescapeHtmlTags() doesn't do its job of replaces all html tags, mostly it will fail to replace
**&lt;a href="mailto:[email protected]"&gt;**ABC Email</a>

so I used this piece of code to fix the issue locally instead of changing the regex used:

fun String.unescapeHtmlTags(): String {
    return this.replace("&lt;", "<").replace("&gt;", ">")
}

and it's working very well now.

Html tags not encoded

Android doesn't support some of the Html tags (eg., <br/>) from string resource. In such cases, it has to be formatted using Html.fromHtml(), which expects the angle brackets in encoded format (&lt;), and set to the corresponding TextView in code. So, if we append html with the key, then the angle brackets have to be in encoded format.

Indonesian is exported to values-id instead of values-in

The strings.xml file for Indonesian is exported to values-id but changing the device language to Indonesian has no effect on the app. If the file is in values-in however, it works as expected.
Another thing I noticed is that both id and in are listed as Indonesian in Android Studio's Translations Editor, but on the phone itself only in is working. This seems to be more of a Java/Android issue, since the official ISO 639-1 is in fact id.

I think this is also the case for Hebrew and Yiddish: https://developer.android.com/reference/java/util/Locale.html#Locale(java.lang.String)

ISO 639 is not a stable standard; some of the language codes it defines (specifically "iw", "ji", and "in") have changed. This constructor accepts both the old codes ("iw", "ji", and "in") and the new codes ("he", "yi", and "id"), but all other API on Locale will return only the OLD codes.

  • IDE version: Android Studio 2021.2.1
  • Plug-in Version: 3.0.0
  • Android version: 12

Some emojis are html encoded

SW details (please complete the following information):

  • IDE version: Android Studio Electric Eel | 2022.1.1
  • Plug-in Version 3.0.0

Summary and background of the bug
We use a lot of emojis on poeditor and when we import terms some emojis are html encoded.
Android Studio is working great with emojis and it would be nice and easier to read if all emojis was not html encoded.

For exemple:

<string name="term_with_emoji">"❤️"</string> 
<string name="term_with_emoji_encoded">"&#x1f622;"</string>

Steps to reproduce
Steps to reproduce the behavior:

  1. Add two terms on poeditor, one with ❤️ (heart emoji) and another with 📍 (pushpin emoji)
  2. Sync the strings
  3. Check generated strings.xm files

Expected behavior
Does not html encode emojis for better readability

Current behavior
Some emojis (but not all) are html encode

Option for disabling HTML entity unescaping

Summary and context of the enhancement
This is a resurrection of the issue #49. In our project we use HTML tags combined with format strings. That means that tags should be escaped. It would be great to have that option.

Suggested implementation
Add an attribute unescapeHtmlTags to poEditor section with default value true, and enable/disable string escaping based on this value.

Additional documentation
https://developer.android.com/guide/topics/resources/string-resource.html#StylingWithHTML

In some cases, however, you may want to create a styled text resource that is also used as a format string. Normally, this doesn't work [...] The work-around to this is to write the HTML tags with escaped entities.

Build fail after Android Gradle Plugin 7.1.0 update

SW details (please complete the following information):

  • IDE version: Android Studio Bumbebee 1.1.0
  • Plug-in Version: 2.4.2

Summary and background of the bug

image

After updating the android gradle plugin from 7.0.4 to 7.1.0, an error occurs when building.
I think ApplicationAndroidComponentsExtension was deprecated.

Steps to reproduce
Steps to reproduce the behavior:

  1. Android Gradle Plugin "com.android.tools.build:gradle:7.1.0" update
  2. Build

Dekekt output is not clear when it fails

SW details (please complete the following information):

  • Plug-in Version: 2.2.1

Summary and background of the bug
As reported by @nokite in #37:

Btw I often have trouble with detekt. Like now when merging master into my branch.
When there's a problem and the commit/merge commit is blocked, the error output is hard to read, it's all in one line 🙂
Maybe it's not correctly set up on my machine, didn't spend time on this.

Here's some output. In this case it's actually pretty short, but normally it also mentions some formatting/complexity warnings etc. (and it's all in one line)

Couldn't check the working tree for unmerged files because of an error. tput: No value for $TERM and no -T specified tput: No value for $TERM and no -T specified tput: No value for $TERM and no -T specified tput: No value for $TERM and no -T specified Running detekt > Task :detekt UP-TO-DATE Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0. Use '--warning-mode all' to show the individual deprecation warnings. See https://docs.gradle.org/6.9/userguide/command_line_interface.html#sec:command_line_warnings BUILD SUCCESSFUL in 814ms 1 actionable task: 1 up-to-date Your code is properly formatted! could not read log file '/Users/.../poeditor-android-gradle-plugin/.git/MERGE_MSG': No such file or directory

java.net.SocketTimeoutException while downloading translations xml file

SW details (please complete the following information):

  • IDE version: any version, also CI
  • Plug-in Version latest version, master, previous versions

Summary and background of the bug
The task importPoEditorStrings fails more than once for all devs with java.net.SocketTimeoutException. This happens way too often and also on CI which breaks our builds.

Steps to reproduce
Steps to reproduce the behavior:

  1. I set up the configuration
  2. I run the task
  3. See the socket timeout error at least once per day

Not-so-helpful stack trace:

* What went wrong:

Execution failed for task ':data-translations:importPoEditorStrings'.

> java.net.SocketTimeoutException: timeout

Expected behavior
Translations should download unless the server is actually down

Current behavior
Task fails to download translations quite often due to SocketTimeoutException

Additional context
Add any other context about the problem here.

Add an ability to disable unescapeHtmlTags() formatting

Summary and context of the enhancement
In our project we use html tags combined with format strings. That means that tags should be escaped. It would be great to have that option.

Suggested implementation
Add an attribute unescapeHtmlTags to poEditor section with default value true. And than in XmlPostProcessor.formatTranslationString() check that attribute

Additional documentation
About
https://developer.android.com/guide/topics/resources/string-resource.html#StylingWithHTML

Multiply projects support

Support for several poeditor project ids
We have split all our strings to several poeditor projects - analytics, links, common strings. It will be great to support several projectId's in configuration.

Doubled "%"

SW details (please complete the following information):

  • IDE version: Android Studio 4.0.1
  • Plug-in Version: 1.0.0
  • System: Windows 10

Summary and background of the bug
All the occurrences of "%" are doubled.

Eg.

  • Authentication error: %s! -> Authentication error: %%s!
  • %1$s - %2$s -> %%1$s - %%2$s

Another problem: all the string have been put into quotation marks

Eg.

  • Profile -> "Profile"

Steps to reproduce
Just execute: gradlew importPoEditorStrings

Expected behavior
I expect that these parameters ("%") string stay unattached

Current behavior
All the occurrences of "%" are doubled.

2020-08-07 12_54_57-strings xml (C__Projects_Android_mv7-android_app_src_main_res_values)  Default C

Have the import the ability to ignore comments

Summary and context of the enhancement
Hello guys, in the configuration of the import

poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
}

I am missing a config property for comments.
My strings import is always with them included, which makes the final resource file kinda bloated.

<string>abc</string>
<!--comment related to "def"-->
<string>def</string>

It is only comments, which is not crucial, but still you can introduce a lot of unnecessary things like TODOs, unrelated information, etc.
I haven't found how to omit them in the import.

Suggested implementation
Just a boolean property on the config would be great.

poEditor {
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
    **importComments = false**
}

Thanks!

Improper format

Great project. I am using to convert my strings from PoEditor. However, I am running into problems with the format of the generated values folders. For example,

values-zh-CN - android studio does not like the format and expects it the CH to be prefaced with an r. Further, any secondary region specifier should be prefaced with an r in my understanding.

Please help.

Simplified and Traditional Chinese are downloaded to folders not accepted by Android

SW details (please complete the following information):

  • Plug-in Version 1.4.1

Gradle 6.2

Build time: 2020-02-17 08:32:01 UTC
Revision: 61d3320259a1a0d31519bf208eb13741679a742f

Kotlin: 1.3.61
Groovy: 2.5.8
Ant: Apache Ant(TM) version 1.10.7 compiled on September 1 2019
JVM: 1.8.0_231 (Oracle Corporation 25.231-b11)
OS: Mac OS X 10.16 x86_64

Summary and background of the bug
Simplified Chinese is downloaded to values-zh-rHANS and Traditional Chinese to values-zh-tHANT. They should probably be in values-b+zh+Hans and values-b+zh+Hant respectively.

Steps to reproduce
Steps to reproduce the behavior:

  1. Add languages Simplified Chinese and Traditional Chinese to your POEditor project
  2. Download translations using this gradle plugin

Expected behavior
Translations should probably be in values-b+zh+Hans and values-b+zh+Hant respectively.

Current behavior
Simplified Chinese is downloaded to values-zh-rHANS and Traditional Chinese to values-zh-tHANT

Disable tablet generation

The XML parser throws some exceptions because of this tablet generation.
Mainly, when there are HTML Strings

Multiple tags intersection doesn't work

SW details (please complete the following information):

  • IDE version: AS Giraffe
  • Plug-in Version 3.4.1

Summary and background of the bug
Tags intersection doesn't work when specifying a list of tags. The plugin just pulls in all strings, not an intersection of the tags as it's supposed to.

I assume this is caused by the way the tags are being sent using Retrofit. They should be sent as tags : ["tagA", "tagB"] (https://poeditor.com/docs/api#projects_export_example_1_button). I haven't been able to check it, but I assume currently they are being sent as tags : tagA, tagB

Steps to reproduce
Steps to reproduce the behavior:

  1. Set up 2 strings on the PoEditor web app using 3 tags (string 1 being added to tags A & B, string 2 being added to tags A & C)
  2. Set up the plugin config to pull in tags A & B
  3. Run the import

Expected behavior
Only string 1 should be pulled.

Current behavior
Currently both strings are pulled.

Not working

`* What went wrong:
Execution failed for task ':app:importPoEditorStrings'.

java.io.IOException: Cannot run program "curl": CreateProcess error=2, The system cannot find the file specified

  • Try:
    Run with --info or --debug option to get more log output.

  • Exception is:
    org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:importPoEditorStrings'.
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:84)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:55)
    at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:62)
    at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
    at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:88)
    at org.gradle.api.internal.tasks.execution.ResolveTaskArtifactStateTaskExecuter.execute(ResolveTaskArtifactStateTaskExecuter.java:46)
    at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:51)
    at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:54)
    at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
    at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:34)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker$1.execute(DefaultTaskGraphExecuter.java:236)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker$1.execute(DefaultTaskGraphExecuter.java:228)
    at org.gradle.internal.Transformers$4.transform(Transformers.java:169)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:106)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:61)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:228)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:215)
    at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.processTask(AbstractTaskPlanExecutor.java:77)
    at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.run(AbstractTaskPlanExecutor.java:58)
    at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.process(DefaultTaskPlanExecutor.java:32)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:113)
    at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:37)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
    at org.gradle.execution.DefaultBuildExecuter.access$000(DefaultBuildExecuter.java:23)
    at org.gradle.execution.DefaultBuildExecuter$1.proceed(DefaultBuildExecuter.java:43)
    at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:32)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:30)
    at org.gradle.initialization.DefaultGradleLauncher$3.execute(DefaultGradleLauncher.java:196)
    at org.gradle.initialization.DefaultGradleLauncher$3.execute(DefaultGradleLauncher.java:193)
    at org.gradle.internal.Transformers$4.transform(Transformers.java:169)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:106)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:56)
    at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:193)
    at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:119)
    at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:102)
    at org.gradle.launcher.exec.GradleBuildController.run(GradleBuildController.java:71)
    at org.gradle.tooling.internal.provider.ExecuteBuildActionRunner.run(ExecuteBuildActionRunner.java:28)
    at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:41)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:26)
    at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:75)
    at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:49)
    at org.gradle.tooling.internal.provider.ServicesSetupBuildActionExecuter.execute(ServicesSetupBuildActionExecuter.java:44)
    at org.gradle.tooling.internal.provider.ServicesSetupBuildActionExecuter.execute(ServicesSetupBuildActionExecuter.java:29)
    at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:67)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:47)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)
    at org.gradle.util.Swapper.swap(Swapper.java:38)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:60)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:72)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
    at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
    at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
    Caused by: org.gradle.api.UncheckedIOException: java.io.IOException: Cannot run program "curl": CreateProcess error=2, The system cannot find the file specified
    at org.gradle.internal.UncheckedException.throwAsUncheckedException(UncheckedException.java:43)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:76)
    at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$StandardTaskAction.doExecute(DefaultTaskClassInfoStore.java:141)
    at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$StandardTaskAction.execute(DefaultTaskClassInfoStore.java:134)
    at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$StandardTaskAction.execute(DefaultTaskClassInfoStore.java:123)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:632)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:615)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:95)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:76)
    ... 70 more
    Caused by: java.io.IOException: Cannot run program "curl": CreateProcess error=2, The system cannot find the file specified
    at com.bq.gradle.ImportPoEditorStringsTask.importPoEditorStrings(ImportPoEditorStringsTask.groovy:57)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73)
    ... 77 more
    Caused by: java.io.IOException: CreateProcess error=2, The system cannot find the file specified
    ... 79 more
    `

Can you provide a fix asap?

invocation of 'Task.project' at execution time is unsupported

SW details (please complete the following information):

  • IDE version: Android Studio 2023.1.1 Patch 1
  • Plug-in Version 4.1.1 - 4.2.0
  • Gradle Version 8.5

Summary and background of the bug

When running the importPoEditorStrings Gradle task, after the files are properly downloaded and saved:

Retrieving project languages...
Available languages: [en]
Retrieving translation file URL for language code: en
Downloading file from URL: https://api.poeditor.com/v2/download/file/...
Saving strings to /Users/.../Workspace/.../app/src/main/res/values

Gradle throws the following error:

FAILURE: Build failed with an exception.

* What went wrong:
Configuration cache problems found in this build.

- Task `:app:importPoEditorStrings` of type `com.hyperdevs.poeditor.gradle.tasks.ImportPoEditorStringsTask`: invocation of 'Task.project' at execution time is unsupported.
  See https://docs.gradle.org/8.5/userguide/configuration_cache.html#config_cache:requirements:use_project_during_execution

I've tested this on Gradle 8.2 and 8.5, same result.
I've tested the following library versions:

  • 4.0.0 doesn't throw any errors, it works perfectly
  • 4.1.0 unfortunately breaks Gradle sync, so I couldn't test it at all
  • 4.1.1 and 4.2.0 throw the error

Add support for library modules

Summary and context of the enhancement
As per discussed in #17, with the removal of resDirPath and the obligation of only being able to use the plugin in application modules in version 1.0.0 of the plug-in, support for library modules was dropped since you could no longer set-up a custom path. This issue aims to add proper support for library modules, so we can have tasks to import PoEditor strings from both application and library modules, and call them using the usual ./gradlew :your_module:importPoEditorStrings.

Suggested implementation
Check if we can implement support for library projects using LibraryExtension and LibraryPlugin

Additional documentation
Check out

Text must not be null or empty

Though my project contains non-empty strings, the plugin throws the following error. If you want I can share my poeditor credentials privately with you. I'm using com.github.imartinez:poeditor-android-gradle-plugin:0.2.6. But, I think that doesn't matter, because I encountered the same error with com.github.bq:poeditor-android-gradle-plugin:0.2.5 also.

`:app:importPoEditorStrings FAILED

FAILURE: Build failed with an exception.

  • What went wrong:
    Execution failed for task ':app:importPoEditorStrings'.

Text must not be null or empty

  • Try:
    Run with --info or --debug option to get more log output.

  • Exception is:
    org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:importPoEditorStrings'.
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:84)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:55)
    at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:62)
    at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
    at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:88)
    at org.gradle.api.internal.tasks.execution.ResolveTaskArtifactStateTaskExecuter.execute(ResolveTaskArtifactStateTaskExecuter.java:46)
    at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:51)
    at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:54)
    at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
    at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:34)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker$1.execute(DefaultTaskGraphExecuter.java:236)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker$1.execute(DefaultTaskGraphExecuter.java:228)
    at org.gradle.internal.Transformers$4.transform(Transformers.java:169)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:106)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:61)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:228)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:215)
    at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.processTask(AbstractTaskPlanExecutor.java:77)
    at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.run(AbstractTaskPlanExecutor.java:58)
    at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.process(DefaultTaskPlanExecutor.java:32)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:113)
    at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:37)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
    at org.gradle.execution.DefaultBuildExecuter.access$000(DefaultBuildExecuter.java:23)
    at org.gradle.execution.DefaultBuildExecuter$1.proceed(DefaultBuildExecuter.java:43)
    at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:32)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:30)
    at org.gradle.initialization.DefaultGradleLauncher$3.execute(DefaultGradleLauncher.java:196)
    at org.gradle.initialization.DefaultGradleLauncher$3.execute(DefaultGradleLauncher.java:193)
    at org.gradle.internal.Transformers$4.transform(Transformers.java:169)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:106)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:56)
    at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:193)
    at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:119)
    at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:102)
    at org.gradle.launcher.exec.GradleBuildController.run(GradleBuildController.java:71)
    at org.gradle.tooling.internal.provider.ExecuteBuildActionRunner.run(ExecuteBuildActionRunner.java:28)
    at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:41)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:26)
    at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:75)
    at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:49)
    at org.gradle.tooling.internal.provider.ServicesSetupBuildActionExecuter.execute(ServicesSetupBuildActionExecuter.java:44)
    at org.gradle.tooling.internal.provider.ServicesSetupBuildActionExecuter.execute(ServicesSetupBuildActionExecuter.java:29)
    at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:67)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:47)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)
    at org.gradle.util.Swapper.swap(Swapper.java:38)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:60)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:72)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
    at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
    at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
    Caused by: java.lang.IllegalArgumentException: Text must not be null or empty
    at com.bq.gradle.ImportPoEditorStringsTask.importPoEditorStrings(ImportPoEditorStringsTask.groovy:58)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73)
    at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$StandardTaskAction.doExecute(DefaultTaskClassInfoStore.java:141)
    at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$StandardTaskAction.execute(DefaultTaskClassInfoStore.java:134)
    at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$StandardTaskAction.execute(DefaultTaskClassInfoStore.java:123)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:632)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:615)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:95)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:76)
    ... 70 more

BUILD FAILED`

Issue when applying the plugin

SW details (please complete the following information):

  • IDE version: Android Studio Giraffe | 2022.3.1 Patch 2
  • Plug-in Version 4.1.0

Summary and background of the bug
When applying the plugin to a project I get the following error:

A problem occurred configuring project ':app'.
> Cannot query the value of extension 'poEditor' property 'enabled' because it has no value available.

Steps to reproduce
Steps to reproduce the behavior:

  1. Sync project with gradle files

I have replicated this on my work project and also tired it in a new bare bones Android project. I have attached the project for replication purposes.

PlaygroundApplication.zip

If you need any more info let me know.

Expected behavior
The sync should pass without error.

Additional context
Add any other context about the problem here.

Remove JCenter before February 2022

SW details:

  • Plug-in Version 2.4.1

Summary and background of the bug
JCenter may stop working in February 2022, so it should be removed from the project. Any dependencies should be fetched from other repositories.

The shutdown date of the repository itself is not clear. In reality it might keep working in read-only mode for a longer time. But no guarantees are given after February.
Furthermore, Gradle 8 will remove jcenter().

https://blog.gradle.org/jcenter-shutdown

Language with empty translations

SW details (please complete the following information):

  • IDE version: Android Studio 4.1.1
  • Plug-in Version 1.4.2

Summary and background of the bug
Languages with empty translations returns empty updated data string from POEditor API which causes an error.

JSON fetched from POEditor:

{
   "response":{
      "status":"success",
      "code":"200",
      "message":"OK"
   },
   "result":{
      "languages":[
         {
            "name":"English",
            "code":"en",
            "translations":2,
            "percentage":100,
            "updated":"2021-02-22T07:32:39+0000"
         },
         {
            "name":"German",
            "code":"de",
            "translations":0,
            "percentage":0,
            "updated":""
         }
      ]
   }
}

Steps to reproduce
Steps to reproduce the behavior:

  1. Set up empty language without translations in POEditor
  2. Try to download translations

Stacktrace

Caused by: java.text.ParseException: Unparseable date: ""
        at com.bq.poeditor.gradle.utils.DateJsonAdapter.fromJson(DateJsonAdapter.kt:39)
        at com.bq.poeditor.gradle.utils.DateJsonAdapter.fromJson(DateJsonAdapter.kt:29)

Expected behavior
Download empty translations without any problem.

Current behavior
Error occurs java.text.ParseException: Unparseable date: "".

ImportPoEditorStrings throws error when retrieving strings. No result coming in Json.

`./gradlew importPoEditorStrings --stacktrace

Task :myProject:importPoEditorStrings FAILED
Retrieving project languages...
An error happened when retrieving strings from project. Please review the plug-in's input parameters and try again

FAILURE: Build failed with an exception.

  • What went wrong:
    Execution failed for task ':myProject:importPoEditorStrings'.

Required value 'result' missing at $

  • Try:

Run with --info or --debug option to get more log output.
Run with --scan to get full insights.

  • Exception is:
    org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':myProject:importPoEditorStrings'.
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:145)
    at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:282)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:143)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:131)
    at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:77)
    at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
    at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
    at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
    at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
    at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
    at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:402)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:389)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:382)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:368)
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
    at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
    at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:61)
    Caused by: com.squareup.moshi.JsonDataException: Required value 'result' missing at $
    at com.squareup.moshi.internal.Util.missingProperty(Util.java:649)
    at com.squareup.moshi.kotlin.reflect.KotlinJsonAdapter.fromJson(KotlinJsonAdapter.kt:103)
    at com.squareup.moshi.internal.NullSafeJsonAdapter.fromJson(NullSafeJsonAdapter.java:41)
    at retrofit2.converter.moshi.MoshiResponseBodyConverter.convert(MoshiResponseBodyConverter.java:46)
    at retrofit2.converter.moshi.MoshiResponseBodyConverter.convert(MoshiResponseBodyConverter.java:27)
    at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:243)
    at retrofit2.OkHttpCall.execute(OkHttpCall.java:204)
    at com.hyperdevs.poeditor.gradle.network.PoEditorApiControllerImpl.getProjectLanguages(PoEditorApiController.kt:54)
    at com.hyperdevs.poeditor.gradle.PoEditorStringsImporter.importPoEditorStrings(PoEditorStringsImporter.kt:101)
    at com.hyperdevs.poeditor.gradle.tasks.ImportPoEditorStringsTask.importPoEditorStrings(ImportPoEditorStringsTask.kt:77)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:104)
    at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:58)
    at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:51)
    at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:29)
    at org.gradle.api.internal.tasks.execution.TaskExecution$2.run(TaskExecution.java:239)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
    at org.gradle.api.internal.tasks.execution.TaskExecution.executeAction(TaskExecution.java:224)
    at org.gradle.api.internal.tasks.execution.TaskExecution.executeActions(TaskExecution.java:207)
    at org.gradle.api.internal.tasks.execution.TaskExecution.executeWithPreviousOutputFiles(TaskExecution.java:190)
    at org.gradle.api.internal.tasks.execution.TaskExecution.execute(TaskExecution.java:168)
    at org.gradle.internal.execution.steps.ExecuteStep.executeInternal(ExecuteStep.java:89)
    at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:40)
    at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:53)
    at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:50)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
    at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:50)
    at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:40)
    at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:68)
    at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:38)
    at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:48)
    at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:36)
    at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:41)
    at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:74)
    at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:55)
    at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:51)
    at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:29)
    at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:61)
    at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:42)
    at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:60)
    at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:27)
    at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:188)
    at org.gradle.internal.execution.steps.BuildCacheStep.lambda$execute$1(BuildCacheStep.java:75)
    at org.gradle.internal.Either$Right.fold(Either.java:175)
    at org.gradle.internal.execution.caching.CachingState.fold(CachingState.java:59)
    at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:73)
    at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:48)
    at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:38)
    at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:27)
    at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:36)
    at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:22)
    at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:109)
    at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$2(SkipUpToDateStep.java:56)
    at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:56)
    at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:38)
    at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:73)
    at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:44)
    at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37)
    at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27)
    at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:89)
    at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:50)
    at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:114)
    at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:57)
    at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:76)
    at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:50)
    at org.gradle.internal.execution.steps.SkipEmptyWorkStep.lambda$execute$2(SkipEmptyWorkStep.java:93)
    at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:93)
    at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:34)
    at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
    at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:43)
    at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:31)
    at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:40)
    at org.gradle.api.internal.tasks.execution.TaskExecution$3.withWorkspace(TaskExecution.java:284)
    at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:40)
    at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:30)
    at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:37)
    at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:27)
    at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:44)
    at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:33)
    at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:76)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:142)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:131)
    at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:77)
    at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
    at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
    at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
    at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
    at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
    at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:402)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:389)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:382)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:368)
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
    at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
    at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:61)
    `

Setting order is not working

SW details (please complete the following information):

  • IDE version: Android Studio Flamingo v9
  • Plug-in Version - 3.1.0

Summary and background of the bug
In the documentation it's mentioned that it's possible to send order in the parameters but I belive in the latest gradle settings it's not working because it's missing a setter. Something like this for example:
fun setDefaultLang(value: String) = defaultLang.set(value)

This is not available for order

Steps to reproduce
Steps to reproduce the behavior:

  1. Add the "order = "terms"" in the poEditor block.
  2. Sync the project
  3. See error

A problem occurred evaluating project ':app'.

Could not set unknown property 'order' for extension 'poEditor' of type com.hyperdevs.poeditor.gradle.PoEditorPluginExtension.

Expected behavior
Value would be set in the request to poeditor and terms would arrive ordered

Current behavior
Crashes. Exception in pasted above.

Additional context
Nothing else.

How can I add the plugin with the new gradle format

Hi there,

Im trying to import the plugin with the new Gradle format like bellow,

plugins {
    id 'com.android.application' version "$gradlePluginVersion" apply false
    id 'com.android.library' version "$gradlePluginVersion" apply false
    id 'org.jetbrains.kotlin.android' version "$kotlinPluginVersion" apply false
  
    id 'com.github.hyperdevs-team.poeditor-android-gradle-plugin' version "$poEditorPluginVersion" apply false
}

But Im always getting the bellow error:

Plugin [id: 'com.github.hyperdevs-team.poeditor-android-gradle-plugin', version: '3.4.0', apply: false] was not found in any of the following sources:

What is the correct ID?

Thank you,

Best regards,
Bernardo

Handle plurals' string resources

Summary and context of the enhancement
Right now the plugin doesn't handle Android plurals properly.

Plurals' syntax is slightly different from the usual <string> nodes, as they follow this structure:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <plurals
        name="plural_name">
        <item
            quantity=["zero" | "one" | "two" | "few" | "many" | "other"]
            >text_string</item>
    </plurals>
</resources>

We need to be able to provide the same functionality that we do for simple strings to plural strings.
It may also be handy to ensure that we add at least one number to the placeholders, but for the initial approach it might not be needed.

Suggested implementation
No suggested implementation.

Additional documentation
Android information about plurals string resources: https://developer.android.com/guide/topics/resources/string-resource#Plurals

Html based strings wrapped by <![CDATA[ ]]> on POEditor are imported without <![CDATA[ ]]> via plugin

SW details

  • IDE version: Android Studio Arctic Fox 2020.3.1
  • Plug-in Version: 2.2.1 - 2.4.0

Summary and background of the bug
Html based strings wrapped by <![CDATA[ ]]> on POEditor are imported without <![CDATA[ ]]> via plugin

Steps to reproduce
Steps to reproduce the behavior:

  1. Add html based string to POEditor project
    e.g: <![CDATA[Some text<a href="%1$s">Link</a> text text]]>
  2. Import the strings in to project
  3. <![CDATA[ ]]> is missed
    e.g:
    <string name="test_string_id">"Some text<a href="%1$s">Link</a> text text"</string>

Expected behavior
Imported strings should contain <![CDATA[ ]]>

Current behavior
<![CDATA[ ]]> is missed

Additional context
When strings are exported via POEditor web page, they contain <![CDATA[ ]]>

Remove/Skip untranslated strings from XML

Summary and context of the enhancement
When importing translations from POEditor, terms that are currently not translated to a given language must be removed.

Android Studio/Gradle's default behavior on blank but defined strings is to actually blank the text out instead of using the default (from values). This causes the app to crash, notably if it uses placeholders.

Add support for multiple build types

Summary and context of the enhancement
Add support for multiple build types' translations in the plugin, so we can import different projects for them.

Suggested implementation
The ideal thing would be to get some configuration like this so we can easily configure it in Gradle scripts:

poEditor {
    // General configuration, in case we don't define build type specific configuration
    apiToken = "your_api_token"
    projectId = 12345
    defaultLang = "en"
    resDirPath = "${project.rootDir}/app/src/main/res"

    // Configuration for flavor1, overrides general configuration
    flavor1 {
        apiToken = "your_api_token"
        projectId = 23456
        defaultLang = "en"
        resDirPath = "${project.rootDir}/app/src/flavor1/res"
    }

    ...
}

Additional documentation
We should check other Gradle plugins so we can get info about how to do this.

Add support to export by tag

Summary and context of the enhancement
As an user I want to download the strings for specific module based on a given tag(s).

Suggested implementation
Added a new configuration to the poEditor receiving an array of tags.
On the poEditor API request send the list of tags to poEditor API, poEditor will filter the strings by tag(s)

Export Documentation

Create proper guidelines and setup instructions for contributors

Summary and context of the enhancement
It would be handy to create some guidelines for setting up the project and how to submit contributions (apart from the PR templates)

Suggested implementation
We should take a look to how open-source projects do this so we can have an idea of what is important for contributors to have in the guidelines.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.