feat: Move strings to resources for localization (#2440)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de> Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net> Co-authored-by: Aunali321 <aunvakil.aa@gmail.com> BREAKING CHANGE: Various APIs have been changed.
This commit is contained in:
committed by
GitHub
co-authored by
oSumAtrIX
semantic-release-bot
Aunali321
parent
15a83a6d28
commit
55fb1ebd9d
@@ -7,7 +7,7 @@ import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.shared.mapping.misc.ResourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
|
||||
@@ -2,15 +2,33 @@ package app.revanced.util
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.util.DomFileEditor
|
||||
import app.revanced.patches.shared.settings.preference.impl.StringResource
|
||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||
import app.revanced.patches.all.misc.resources.AddResourcesPatch
|
||||
import app.revanced.util.resource.BaseResource
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.NodeList
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
private val classLoader = object {}.javaClass.classLoader
|
||||
|
||||
/**
|
||||
* Returns a sequence for all child nodes.
|
||||
*/
|
||||
fun NodeList.asSequence() = (0 until this.length).asSequence().map { this.item(it) }
|
||||
|
||||
/**
|
||||
* Returns a sequence for all child nodes.
|
||||
*/
|
||||
fun Node.childElementsSequence() = this.childNodes.asSequence().filter { it.nodeType == Node.ELEMENT_NODE }
|
||||
|
||||
/**
|
||||
* Performs the given [action] on each child element.
|
||||
*/
|
||||
fun Node.forEachChildElement(action: (Node) -> Unit) = childElementsSequence().forEach {
|
||||
action(it)
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively traverse the DOM tree starting from the given root node.
|
||||
*
|
||||
@@ -21,26 +39,6 @@ fun Node.doRecursively(action: (Node) -> Unit) {
|
||||
for (i in 0 until this.childNodes.length) this.childNodes.item(i).doRecursively(action)
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge strings. This manages [StringResource]s automatically.
|
||||
*
|
||||
* @param host The hosting xml resource. Needs to be a valid strings.xml resource.
|
||||
*/
|
||||
fun ResourceContext.mergeStrings(host: String) {
|
||||
this.iterateXmlNodeChildren(host, "resources") {
|
||||
// TODO: figure out why this is needed
|
||||
if (!it.hasAttributes()) return@iterateXmlNodeChildren
|
||||
|
||||
val attributes = it.attributes
|
||||
val key = attributes.getNamedItem("name")!!.nodeValue!!
|
||||
val value = it.textContent!!
|
||||
|
||||
val formatted = attributes.getNamedItem("formatted") == null
|
||||
|
||||
SettingsPatch.addString(key, value, formatted)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy resources from the current class loader to the resource directory.
|
||||
*
|
||||
@@ -112,4 +110,16 @@ fun String.copyXmlNode(source: DomFileEditor, target: DomFileEditor): AutoClosea
|
||||
source.close()
|
||||
target.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a resource node child.
|
||||
*
|
||||
* @param resource The resource to add.
|
||||
* @param resourceCallback Called when a resource has been processed.
|
||||
*/
|
||||
internal fun Node.addResource(resource: BaseResource, resourceCallback: (BaseResource) -> Unit = { }) {
|
||||
appendChild(resource.serialize(ownerDocument, resourceCallback))
|
||||
}
|
||||
|
||||
internal fun DomFileEditor?.getNode(tagName: String) = this!!.file.getElementsByTagName(tagName).item(0)
|
||||
@@ -0,0 +1,37 @@
|
||||
package app.revanced.util.resource
|
||||
|
||||
import app.revanced.util.childElementsSequence
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
|
||||
/**
|
||||
* An array resource.
|
||||
*
|
||||
* @param name The name of the array resource.
|
||||
* @param items The items of the array resource.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class ArrayResource(
|
||||
name: String,
|
||||
val items: List<String>,
|
||||
) : BaseResource(name, "string-array") {
|
||||
override fun serialize(ownerDocument: Document, resourceCallback: (BaseResource) -> Unit) =
|
||||
super.serialize(ownerDocument, resourceCallback).apply {
|
||||
setAttribute("name", name)
|
||||
|
||||
items.forEach { item ->
|
||||
appendChild(ownerDocument.createElement("item").also { itemNode ->
|
||||
itemNode.textContent = item
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromNode(node: Node): ArrayResource {
|
||||
val key = node.attributes.getNamedItem("name").textContent
|
||||
val items = node.childElementsSequence().map { it.textContent }.toList()
|
||||
|
||||
return ArrayResource(key, items)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package app.revanced.util.resource
|
||||
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Element
|
||||
|
||||
/**
|
||||
* Base resource class for all resources.
|
||||
*
|
||||
* @param name The name of the resource.
|
||||
* @param tag The tag of the resource.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
abstract class BaseResource(
|
||||
val name: String,
|
||||
val tag: String
|
||||
) {
|
||||
/**
|
||||
* Serialize resource element to XML.
|
||||
* Overriding methods should invoke super and operate on its return value.
|
||||
* @param ownerDocument Target document to create elements from.
|
||||
* @param resourceCallback Called when a resource has been processed.
|
||||
*/
|
||||
open fun serialize(ownerDocument: Document, resourceCallback: (BaseResource) -> Unit = { }): Element {
|
||||
return ownerDocument.createElement(tag).apply {
|
||||
setAttribute("name", name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name.hashCode()
|
||||
result = 31 * result + tag.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as BaseResource
|
||||
|
||||
return name == other.name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package app.revanced.util.resource
|
||||
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
|
||||
/**
|
||||
* A string value.
|
||||
* Represents a string in the strings.xml file.
|
||||
*
|
||||
* @param name The name of the string.
|
||||
* @param value The value of the string.
|
||||
* @param formatted If the string is formatted. Defaults to `true`.
|
||||
*/
|
||||
class StringResource(
|
||||
name: String,
|
||||
val value: String,
|
||||
val formatted: Boolean = true,
|
||||
) : BaseResource(name, "string") {
|
||||
override fun serialize(ownerDocument: Document, resourceCallback: (BaseResource) -> Unit) =
|
||||
super.serialize(ownerDocument, resourceCallback).apply {
|
||||
// if the string is un-formatted, explicitly add the formatted attribute
|
||||
if (!formatted) setAttribute("formatted", "false")
|
||||
|
||||
if (value.contains(Regex("(?<!\\\\)['\"]")))
|
||||
throw PatchException("String $name cannot contain unescaped quotes in value \"$value\".")
|
||||
|
||||
textContent = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromNode(node: Node): StringResource {
|
||||
val name = node.attributes.getNamedItem("name").textContent
|
||||
val value = node.textContent
|
||||
// TODO: Should this be true by default? It is too in the constructor of StringResource.
|
||||
val formatted = node.attributes.getNamedItem("formatted")?.textContent?.toBoolean() ?: true
|
||||
|
||||
return StringResource(name, value, formatted)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user