mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-22 18:23:59 +00:00
ci: Properly implement Crowdin strings processing
This commit is contained in:
4
.github/workflows/push_strings.yml
vendored
4
.github/workflows/push_strings.yml
vendored
@@ -16,10 +16,10 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Preprocess strings
|
||||
- name: Process strings
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew clean preprocessCrowdinStrings
|
||||
run: ./gradlew processStringsForCrowdin
|
||||
|
||||
- name: Push strings
|
||||
uses: crowdin/github-action@v2
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
import org.w3c.dom.*
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.OutputKeys
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
|
||||
group = "app.revanced"
|
||||
|
||||
patches {
|
||||
@@ -22,25 +29,6 @@ dependencies {
|
||||
compileOnly(project(":patches:stub"))
|
||||
}
|
||||
|
||||
tasks {
|
||||
register<JavaExec>("preprocessCrowdinStrings") {
|
||||
description = "Preprocess strings for Crowdin push"
|
||||
|
||||
dependsOn(compileKotlin)
|
||||
|
||||
classpath = sourceSets["main"].runtimeClasspath
|
||||
mainClass.set("app.revanced.util.CrowdinPreprocessorKt")
|
||||
|
||||
args = listOf(
|
||||
"src/main/resources/addresources/values/strings.xml",
|
||||
// Ideally this would use build/tmp/crowdin/strings.xml
|
||||
// But using that does not work with Crowdin pull because
|
||||
// it does not recognize the strings.xml file belongs to this project.
|
||||
"src/main/resources/addresources/values/strings.xml"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs = listOf("-Xcontext-receivers")
|
||||
@@ -55,4 +43,91 @@ publishing {
|
||||
credentials(PasswordCredentials::class)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("processStringsForCrowdin") {
|
||||
description = "Process strings file for Crowdin by commenting out non-standard tags."
|
||||
|
||||
doLast {
|
||||
// Comment out the non-standard tags. Otherwise, Crowdin interprets the file
|
||||
// not as Android but instead a generic xml file where strings are
|
||||
// identified by xml position and not key
|
||||
val stringsXmlFile = project.projectDir.resolve("src/main/resources/addresources/values/strings.xml")
|
||||
|
||||
val builder = DocumentBuilderFactory.newInstance().apply {
|
||||
isIgnoringComments = false
|
||||
isCoalescing = false
|
||||
isNamespaceAware = false
|
||||
}.newDocumentBuilder()
|
||||
|
||||
val document = builder.newDocument()
|
||||
val root = document.createElement("resources").also(document::appendChild)
|
||||
|
||||
fun walk(node: Node, appId: String? = null, patchId: String? = null, insideResources: Boolean = false) {
|
||||
fun walkChildren(el: Element, appId: String?, patchId: String?, insideResources: Boolean) {
|
||||
val children = el.childNodes
|
||||
for (i in 0 until children.length) {
|
||||
walk(children.item(i), appId, patchId, insideResources)
|
||||
}
|
||||
}
|
||||
when (node.nodeType) {
|
||||
Node.COMMENT_NODE -> {
|
||||
val comment = document.createComment(node.nodeValue)
|
||||
if (insideResources) root.appendChild(comment) else document.insertBefore(comment, root)
|
||||
}
|
||||
|
||||
Node.ELEMENT_NODE -> {
|
||||
val element = node as Element
|
||||
|
||||
when (element.tagName) {
|
||||
"resources" -> walkChildren(element, appId, patchId, insideResources = true)
|
||||
|
||||
"app" -> {
|
||||
val newAppId = element.getAttribute("id")
|
||||
|
||||
root.appendChild(document.createComment(" <app id=\"$newAppId\"> "))
|
||||
walkChildren(element, newAppId, patchId, insideResources)
|
||||
root.appendChild(document.createComment(" </app> "))
|
||||
}
|
||||
|
||||
"patch" -> {
|
||||
val newPatchId = element.getAttribute("id")
|
||||
|
||||
root.appendChild(document.createComment(" <patch id=\"$newPatchId\"> "))
|
||||
walkChildren(element, appId, newPatchId, insideResources)
|
||||
root.appendChild(document.createComment(" </patch> "))
|
||||
}
|
||||
|
||||
"string" -> {
|
||||
val name = element.getAttribute("name")
|
||||
val value = element.textContent
|
||||
val fullName = "$appId.$patchId.$name"
|
||||
|
||||
val stringElement = document.createElement("string")
|
||||
stringElement.setAttribute("name", fullName)
|
||||
stringElement.appendChild(document.createTextNode(value))
|
||||
root.appendChild(stringElement)
|
||||
}
|
||||
|
||||
else -> walkChildren(element, appId, patchId, insideResources)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.parse(stringsXmlFile).let {
|
||||
val topLevel = it.childNodes
|
||||
for (i in 0 until topLevel.length) {
|
||||
val node = topLevel.item(i)
|
||||
if (node != it.documentElement) walk(node)
|
||||
}
|
||||
|
||||
walk(it.documentElement)
|
||||
}
|
||||
|
||||
TransformerFactory.newInstance().newTransformer().apply {
|
||||
setOutputProperty(OutputKeys.INDENT, "yes")
|
||||
setOutputProperty(OutputKeys.ENCODING, "utf-8")
|
||||
}.transform(DOMSource(document), StreamResult(stringsXmlFile))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package app.revanced.util
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Comments out the non-standard <app> and <patch> tags.
|
||||
*
|
||||
* Previously this was done on Crowdin after pushing.
|
||||
* But Crowdin preprocessing has randomly failed but still used the unmodified
|
||||
* strings.xml file, which effectively deletes all patch strings from Crowdin.
|
||||
*/
|
||||
internal fun main(args: Array<String>) {
|
||||
if (args.size != 2) {
|
||||
throw RuntimeException("Exactly two arguments are required: <input_file> <output_file>")
|
||||
}
|
||||
|
||||
val inputFilePath = args[0]
|
||||
val inputFile = File(inputFilePath)
|
||||
if (!inputFile.exists()) {
|
||||
throw RuntimeException(
|
||||
"Input file not found: $inputFilePath currentDirectory: " + File(".").canonicalPath
|
||||
)
|
||||
}
|
||||
|
||||
// Comment out the non-standard tags. Otherwise Crowdin interprets the file
|
||||
// not as Android but instead a generic xml file where strings are
|
||||
// identified by xml position and not key.
|
||||
val content = inputFile.readText()
|
||||
val tagRegex = """((<app\s+.*>)|(</app>)|(<patch\s+.*>)|(</patch>))""".toRegex()
|
||||
val modifiedContent = content.replace(tagRegex, """<!-- $1 -->""")
|
||||
|
||||
// Write modified content to the output file (creates file if it doesn't exist).
|
||||
val outputFilePath = args[1]
|
||||
val outputFile = File(outputFilePath)
|
||||
outputFile.parentFile?.mkdirs()
|
||||
outputFile.writeText(modifiedContent)
|
||||
|
||||
println("Preprocessed strings.xml to: $outputFilePath")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user