Skip to content

Commit 0fd785f

Browse files
committed
Migrate to PoEditor API v2
1 parent 0e2aec4 commit 0fd785f

File tree

10 files changed

+178
-96
lines changed

10 files changed

+178
-96
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
### Added
2525
- No new features!
2626
### Changed
27-
- No changed features!
27+
- Migrate to PoEditor API v2
2828
### Deprecated
2929
- No deprecated features!
3030
### Removed

build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,12 @@ dependencies {
6363

6464
implementation("com.squareup.moshi:moshi:1.9.2")
6565
implementation("com.squareup.moshi:moshi-kotlin:1.9.2")
66+
implementation("com.squareup.moshi:moshi-adapters:1.9.2")
6667

6768
implementation("com.squareup.retrofit2:retrofit:2.9.0")
6869
implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
6970

71+
7072
implementation("com.squareup.okhttp3:logging-interceptor:4.7.2")
7173
implementation("com.squareup.okhttp3:okhttp:4.7.2")
7274

src/main/kotlin/com/hyperdevs/poeditor/gradle/PoEditorStringsImporter.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@
1919
package com.hyperdevs.poeditor.gradle
2020

2121
import com.hyperdevs.poeditor.gradle.network.api.PoEditorApi
22-
import com.hyperdevs.poeditor.gradle.utils.DateJsonAdapter
2322
import com.hyperdevs.poeditor.gradle.network.PoEditorApiControllerImpl
2423
import com.hyperdevs.poeditor.gradle.utils.TABLET_REGEX_STRING
2524
import com.hyperdevs.poeditor.gradle.ktx.downloadUrlToString
25+
import com.hyperdevs.poeditor.gradle.network.api.ExportType
2626
import com.hyperdevs.poeditor.gradle.utils.logger
2727
import com.hyperdevs.poeditor.gradle.xml.AndroidXmlWriter
2828
import com.hyperdevs.poeditor.gradle.xml.XmlPostProcessor
2929
import com.squareup.moshi.Moshi
30+
import com.squareup.moshi.adapters.PoEditorDateJsonAdapter
3031
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
3132
import okhttp3.HttpUrl.Companion.toHttpUrl
3233
import okhttp3.OkHttpClient
@@ -40,11 +41,11 @@ import java.util.concurrent.TimeUnit
4041
* Main class that does the XML download, parsing and saving from PoEditor files.
4142
*/
4243
object PoEditorStringsImporter {
43-
private const val POEDITOR_API_URL = "https://poeditor.com/api/"
44+
private const val POEDITOR_API_URL = "https://api.poeditor.com/v2/"
4445

4546
private val moshi = Moshi.Builder()
4647
.add(KotlinJsonAdapterFactory())
47-
.add(Date::class.java, DateJsonAdapter())
48+
.add(Date::class.java, PoEditorDateJsonAdapter())
4849
.build()
4950

5051
private const val CONNECT_TIMEOUT_SECONDS = 30L
@@ -98,7 +99,7 @@ object PoEditorStringsImporter {
9899
val translationFileUrl = poEditorApiController.getTranslationFileUrl(
99100
projectId = projectId,
100101
code = languageCode,
101-
type = "android_strings",
102+
type = ExportType.ANDROID_STRINGS,
102103
tags = tags)
103104

104105
// Download translation File to in-memory string

src/main/kotlin/com/hyperdevs/poeditor/gradle/ktx/DocumentExtensions.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,17 @@ private val DEFAULT_ENCODING = Charsets.UTF_8
2929

3030
/**
3131
* Converts an XML string to a proper [Document].
32+
* If the string is an empty string, it generates a basic <resources> XML.
3233
*/
33-
fun String.toDocument(): Document =
34-
DocumentBuilderFactory.newInstance()
34+
fun String.toStringsXmlDocument(): Document {
35+
val xmlString = this.ifBlank {
36+
"<resources></resources>"
37+
}
38+
39+
return DocumentBuilderFactory.newInstance()
3540
.newDocumentBuilder()
36-
.parse(this.byteInputStream(DEFAULT_ENCODING))
41+
.parse(xmlString.byteInputStream(DEFAULT_ENCODING))
42+
}
3743

3844
/**
3945
* Converts a [Document] into a formatted [String].

src/main/kotlin/com/hyperdevs/poeditor/gradle/network/PoEditorApiController.kt

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package com.hyperdevs.poeditor.gradle.network
2020

21+
import com.hyperdevs.poeditor.gradle.network.api.ExportType
2122
import com.hyperdevs.poeditor.gradle.network.api.PoEditorApi
2223
import com.hyperdevs.poeditor.gradle.network.api.PoEditorResponse
2324
import com.hyperdevs.poeditor.gradle.network.api.ProjectLanguage
@@ -38,7 +39,7 @@ interface PoEditorApiController {
3839
*/
3940
fun getTranslationFileUrl(projectId: Int,
4041
code: String,
41-
type: String,
42+
type: ExportType,
4243
tags: List<String>?): String
4344
}
4445

@@ -51,27 +52,20 @@ class PoEditorApiControllerImpl(private val apiToken: String,
5152
val response = poEditorApi.getProjectLanguages(
5253
apiToken = apiToken,
5354
id = projectId).execute()
54-
return response.onSuccessful { it.list }
55+
return response.onSuccessful { it.result.languages }
5556
}
5657

57-
override fun getTranslationFileUrl(projectId: Int, code: String, type: String, tags: List<String>?): String {
58+
override fun getTranslationFileUrl(projectId: Int, code: String, type: ExportType, tags: List<String>?): String {
5859
val response = poEditorApi.getExportFileInfo(
5960
apiToken = apiToken,
6061
id = projectId,
61-
type = type,
62+
type = type.toString().toLowerCase(),
6263
language = code,
63-
tags = processTags(tags))
64+
tags = tags)
6465
.execute()
65-
return response.onSuccessful { it.item }
66+
return response.onSuccessful { it.result.url }
6667
}
6768

68-
/**
69-
* Tags must be in the format: ["tag1", "tag2", ... , "tagN"]
70-
* Check the documentation for details: https://poeditor.com/api_reference/#export
71-
*/
72-
private fun processTags(tags: List<String>?): String? =
73-
tags?.takeIf { it.isNotEmpty() }?.joinToString { "\"$it\"" }?.let { "[$it]" }
74-
7569
private inline fun <T : PoEditorResponse, U> Response<T>.onSuccessful(func: (T) -> U): U {
7670
if (isSuccessful && body()?.response?.code == "200") {
7771
body()?.let { return func(it) }

src/main/kotlin/com/hyperdevs/poeditor/gradle/network/api/PoEditorApi.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,21 @@ interface PoEditorApi {
3030
* Returns a list of languages that the current PoEditor project contains.
3131
*/
3232
@FormUrlEncoded
33-
@POST("/api/")
33+
@POST("languages/list")
3434
fun getProjectLanguages(@Field("api_token") apiToken: String,
35-
@Field("action") action: String = "list_languages",
36-
@Field("id") id: Int): Call<ListProjectLanguagesResponse>
35+
@Field("id") id: Int): Call<ListLanguagesResponse>
3736

3837
/**
3938
* Returns the exportables ready to retrieve from the current PoEditor project.
4039
*/
4140
@FormUrlEncoded
42-
@POST("/api/")
41+
@POST("projects/export")
4342
fun getExportFileInfo(@Field("api_token") apiToken: String,
4443
@Field("id") id: Int,
45-
@Field("action") action: String = "export",
46-
@Field("type") type: String,
4744
@Field("language") language: String,
48-
@Field("tags") tags: String?): Call<ExportResponse>
45+
@Field("type") type: String,
46+
@Field("filters") filters: List<String>? = null,
47+
@Field("order") order: String? = null,
48+
@Field("tags") tags: List<String>? = null,
49+
@Field("options") options: Map<String, String>? = null): Call<ExportResponse>
4950
}

src/main/kotlin/com/hyperdevs/poeditor/gradle/network/api/PoEditorApiModels.kt

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,29 +25,91 @@ import java.util.Date
2525
*/
2626
open class PoEditorResponse(open val response: ResponseStatus)
2727

28+
/**
29+
* Basic response data.
30+
*/
31+
data class ResponseStatus(val status: String,
32+
val code: String,
33+
val message: String)
34+
2835
/**
2936
* PoEditor response to "list languages" call.
3037
*/
31-
data class ListProjectLanguagesResponse(override val response: ResponseStatus,
32-
val list: List<ProjectLanguage>) : PoEditorResponse(response)
38+
data class ListLanguagesResponse(override val response: ResponseStatus,
39+
val result: ListLanguagesResult) : PoEditorResponse(response)
40+
41+
/**
42+
* Result of a "list language" call.
43+
*/
44+
data class ListLanguagesResult(val languages: List<ProjectLanguage>)
3345

3446
/**
3547
* PoEditor response to "export languages" call.
3648
*/
3749
data class ExportResponse(override val response: ResponseStatus,
38-
val item: String) : PoEditorResponse(response)
50+
val result: ExportResult) : PoEditorResponse(response)
3951

4052
/**
41-
* Basic response data.
53+
* Result of a "list language" call.
4254
*/
43-
data class ResponseStatus(val status: String,
44-
val code: String,
45-
val message: String)
55+
data class ExportResult(val url: String)
4656

4757
/**
4858
* Information about a language in PoEditor.
4959
*/
5060
data class ProjectLanguage(val name: String,
5161
val code: String,
62+
val translations: Int,
5263
val percentage: Double,
53-
val updated: Date?)
64+
val updated: Date?)
65+
66+
/**
67+
* Types of file export allowed in PoEditor.
68+
*/
69+
enum class ExportType {
70+
PO,
71+
POT,
72+
MO,
73+
XLS,
74+
XLSX,
75+
CSV,
76+
INI,
77+
RESW,
78+
RESX,
79+
ANDROID_STRINGS,
80+
APPLE_STRINGS,
81+
XLIFF,
82+
PROPERTIES,
83+
KEY_VALUE_JSON,
84+
JSON,
85+
YML,
86+
XLF,
87+
XMB,
88+
XTB,
89+
ARB,
90+
RISE_360_XLIFF;
91+
92+
companion object {
93+
/** Returns the enum value associated to a string value. */
94+
fun from(filterString: String) = valueOf(filterString.toUpperCase())
95+
}
96+
}
97+
98+
/**
99+
* Filter types to use in file exports.
100+
*/
101+
enum class FilterType {
102+
TRANSLATED,
103+
UNTRANSLATED,
104+
FUZZY,
105+
NOT_FUZZY,
106+
AUTOMATIC,
107+
NOT_AUTOMATIC,
108+
PROOFREAD,
109+
NOT_PROOFREAD;
110+
111+
companion object {
112+
/** Returns the enum value associated to a string value. */
113+
fun from(filterString: String) = valueOf(filterString.toUpperCase())
114+
}
115+
}

src/main/kotlin/com/hyperdevs/poeditor/gradle/utils/DateJsonAdapter.kt

Lines changed: 0 additions & 51 deletions
This file was deleted.

src/main/kotlin/com/hyperdevs/poeditor/gradle/xml/XmlPostProcessor.kt

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,13 @@
1919
package com.hyperdevs.poeditor.gradle.xml
2020

2121
import com.hyperdevs.poeditor.gradle.ktx.toAndroidXmlString
22-
import com.hyperdevs.poeditor.gradle.ktx.toDocument
22+
import com.hyperdevs.poeditor.gradle.ktx.toStringsXmlDocument
2323
import com.hyperdevs.poeditor.gradle.ktx.unescapeHtmlTags
2424
import com.hyperdevs.poeditor.gradle.utils.ALL_REGEX_STRING
2525
import org.w3c.dom.Document
2626
import org.w3c.dom.Element
2727
import org.w3c.dom.Node
2828
import org.w3c.dom.NodeList
29-
import javax.xml.parsers.DocumentBuilderFactory
3029

3130
/**
3231
* Class that handles XML transformation.
@@ -61,7 +60,7 @@ class XmlPostProcessor {
6160
*/
6261
fun formatTranslationXml(translationFileXmlString: String): String {
6362
// Parse line by line by traversing the original file using DOM
64-
val translationFileXmlDocument = translationFileXmlString.toDocument()
63+
val translationFileXmlDocument = translationFileXmlString.toStringsXmlDocument()
6564

6665
formatTranslationXmlDocument(translationFileXmlDocument, translationFileXmlDocument.childNodes)
6766

@@ -101,9 +100,7 @@ class XmlPostProcessor {
101100
*/
102101
fun splitTranslationXml(translationXmlString: String,
103102
fileSplitRegexStringList: List<String>): Map<String, Document> {
104-
val translationFileRecords = DocumentBuilderFactory.newInstance()
105-
.newDocumentBuilder()
106-
.parse(translationXmlString.byteInputStream(DEFAULT_ENCODING))
103+
val translationFileRecords = translationXmlString.toStringsXmlDocument()
107104

108105
return fileSplitRegexStringList
109106
.map { regex ->
@@ -115,9 +112,7 @@ class XmlPostProcessor {
115112
.mapValues { (regexString, nodes) ->
116113
val regex = Regex(regexString)
117114
val xmlString = "<resources></resources>"
118-
val xmlRecords = DocumentBuilderFactory.newInstance()
119-
.newDocumentBuilder()
120-
.parse(xmlString.byteInputStream(DEFAULT_ENCODING))
115+
val xmlRecords = xmlString.toStringsXmlDocument()
121116
nodes.forEach { node ->
122117
node.parentNode.removeChild(node)
123118
val copiedNode = (node.cloneNode(true) as Element).apply {

0 commit comments

Comments
 (0)