mirror of
https://github.com/BetterAndroid/FlexiLocale.git
synced 2025-09-05 18:55:17 +08:00
fix: project folder naming
This commit is contained in:
2
flexilocale-gradle-plugin/.gitignore
vendored
Normal file
2
flexilocale-gradle-plugin/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.gradle
|
||||
/build
|
66
flexilocale-gradle-plugin/build.gradle.kts
Normal file
66
flexilocale-gradle-plugin/build.gradle.kts
Normal file
@@ -0,0 +1,66 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
autowire(libs.plugins.kotlin.jvm)
|
||||
autowire(libs.plugins.maven.publish)
|
||||
}
|
||||
|
||||
allprojects {
|
||||
group = property.project.groupName
|
||||
version = property.project.version
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
sourceSets.all { languageSettings { languageVersion = "2.0" } }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(com.android.library.com.android.library.gradle.plugin)
|
||||
compileOnly(org.jetbrains.kotlin.kotlin.gradle.plugin)
|
||||
implementation(com.squareup.kotlinpoet)
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create(property.project.moduleName) {
|
||||
id = property.project.groupName
|
||||
implementationClass = property.gradle.plugin.implementationClass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mavenPublishing {
|
||||
coordinates(property.project.groupName, property.project.moduleName, property.project.version)
|
||||
pom {
|
||||
name = property.project.name
|
||||
description = property.project.description
|
||||
url = property.project.url
|
||||
licenses {
|
||||
license {
|
||||
name = property.project.licence.name
|
||||
url = property.project.licence.url
|
||||
distribution = property.project.licence.url
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = property.project.developer.id
|
||||
name = property.project.developer.name
|
||||
email = property.project.developer.email
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = property.maven.publish.scm.url
|
||||
connection = property.maven.publish.scm.connection
|
||||
developerConnection = property.maven.publish.scm.developerConnection
|
||||
}
|
||||
}
|
||||
publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.S01)
|
||||
signAllPublications()
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/10.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.highcapable.flexilocale
|
||||
|
||||
import com.highcapable.flexilocale.generated.FlexiLocaleProperties
|
||||
|
||||
/**
|
||||
* [FlexiLocale] 的装载调用类
|
||||
*/
|
||||
object FlexiLocale {
|
||||
|
||||
/** 标签名称 */
|
||||
const val TAG = FlexiLocaleProperties.PROJECT_NAME
|
||||
|
||||
/** 版本 */
|
||||
const val VERSION = FlexiLocaleProperties.PROJECT_VERSION
|
||||
|
||||
/** 项目地址 */
|
||||
const val PROJECT_URL = FlexiLocaleProperties.PROJECT_URL
|
||||
}
|
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/11.
|
||||
*/
|
||||
@file:Suppress("unused", "USELESS_CAST", "KotlinRedundantDiagnosticSuppress")
|
||||
|
||||
package com.highcapable.flexilocale.gradle.factory
|
||||
|
||||
import com.highcapable.flexilocale.utils.debug.FError
|
||||
import com.highcapable.flexilocale.utils.factory.camelcase
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.plugins.ExtensionAware
|
||||
|
||||
/**
|
||||
* 创建、获取扩展方法
|
||||
* @param name 方法名称 - 自动调用 [toSafeExtName]
|
||||
* @param clazz 目标对象 [Class]
|
||||
* @param args 方法参数
|
||||
* @return [ExtensionAware]
|
||||
*/
|
||||
internal fun ExtensionAware.getOrCreate(name: String, clazz: Class<*>, vararg args: Any?) = name.toSafeExtName().let { sName ->
|
||||
runCatching { extensions.create(sName, clazz, *args).asExtension() }.getOrElse {
|
||||
if (!(it is IllegalArgumentException && it.message?.startsWith("Cannot add extension with name") == true)) throw it
|
||||
runCatching { extensions.getByName(sName).asExtension() }.getOrNull() ?: FError.make("Create or get extension failed with name \"$sName\"")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建、获取扩展方法 - 目标对象 [T]
|
||||
* @param name 方法名称 - 自动调用 [toSafeExtName]
|
||||
* @param args 方法参数
|
||||
* @return [T]
|
||||
*/
|
||||
internal inline fun <reified T> ExtensionAware.getOrCreate(name: String, vararg args: Any?) = name.toSafeExtName().let { sName ->
|
||||
runCatching { extensions.create(sName, T::class.java, *args) as T }.getOrElse {
|
||||
if (!(it is IllegalArgumentException && it.message?.startsWith("Cannot add extension with name") == true)) throw it
|
||||
runCatching { extensions.getByName(sName) as? T? }.getOrNull() ?: FError.make("Create or get extension failed with name \"$sName\"")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扩展方法
|
||||
* @param name 方法名称
|
||||
* @return [ExtensionAware]
|
||||
*/
|
||||
internal fun ExtensionAware.get(name: String) =
|
||||
runCatching { extensions.getByName(name).asExtension() }.getOrNull() ?: FError.make("Could not get extension with name \"$name\"")
|
||||
|
||||
/**
|
||||
* 获取扩展方法 - 目标对象 [T]
|
||||
* @param name 方法名称
|
||||
* @return [T]
|
||||
*/
|
||||
internal inline fun <reified T> ExtensionAware.get(name: String) =
|
||||
runCatching { extensions.getByName(name) as T }.getOrNull() ?: FError.make("Could not get extension with name \"$name\"")
|
||||
|
||||
/**
|
||||
* 获取扩展方法 - 目标对象 [T]
|
||||
* @return [T]
|
||||
*/
|
||||
internal inline fun <reified T> ExtensionAware.get() =
|
||||
runCatching { extensions.getByType(T::class.java) as T }.getOrNull() ?: FError.make("Could not get extension with type ${T::class.java}")
|
||||
|
||||
/**
|
||||
* 配置扩展方法 - 目标对象 [T]
|
||||
* @param name 方法名称
|
||||
* @param configure 配置方法体
|
||||
*/
|
||||
internal inline fun <reified T> ExtensionAware.configure(name: String, configure: Action<T>) = extensions.configure(name, configure)
|
||||
|
||||
/**
|
||||
* 是否存在扩展方法
|
||||
* @param name 方法名称
|
||||
* @return [Boolean]
|
||||
*/
|
||||
internal fun ExtensionAware.hasExtension(name: String) = runCatching { extensions.getByName(name); true }.getOrNull() ?: false
|
||||
|
||||
/**
|
||||
* 转换到扩展方法类型 [ExtensionAware]
|
||||
* @return [ExtensionAware]
|
||||
* @throws IllegalStateException 如果类型不是 [ExtensionAware]
|
||||
*/
|
||||
internal fun Any.asExtension() = this as? ExtensionAware? ?: FError.make("This instance \"$this\" is not a valid Extension")
|
||||
|
||||
/**
|
||||
* 由于 Gradle 存在一个 [ExtensionAware] 的扩展
|
||||
*
|
||||
* 此功能用于检测当前字符串是否为 Gradle 使用的关键字名称
|
||||
* @return [Boolean]
|
||||
*/
|
||||
internal fun String.isUnSafeExtName() = camelcase().let { it == "ext" || it == "extra" || it == "extraProperties" || it == "extensions" }
|
||||
|
||||
/**
|
||||
* 由于 Gradle 存在一个 [ExtensionAware] 的扩展
|
||||
*
|
||||
* 此功能用于转换不符合规定的字符串到 "{字符串}s"
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.toSafeExtName() = if (isUnSafeExtName()) "${this}s" else this
|
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/11.
|
||||
*/
|
||||
package com.highcapable.flexilocale.gradle.factory
|
||||
|
||||
import org.gradle.api.Project
|
||||
|
||||
/**
|
||||
* 获取指定项目的完整名称 (无子项目前冒号)
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun Project.fullName(): String {
|
||||
val baseNames = mutableListOf<String>()
|
||||
|
||||
/**
|
||||
* 递归子项目
|
||||
* @param project 当前项目
|
||||
*/
|
||||
fun fetchChild(project: Project) {
|
||||
project.parent?.also { if (it != it.rootProject) fetchChild(it) }
|
||||
baseNames.add(project.name)
|
||||
}; fetchChild(project = this)
|
||||
return buildString { baseNames.onEach { append(":$it") }.clear() }.drop(1)
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/10.
|
||||
*/
|
||||
package com.highcapable.flexilocale.gradle.proxy
|
||||
|
||||
import org.gradle.api.Project
|
||||
|
||||
/**
|
||||
* Gradle [Project] 生命周期接口
|
||||
*/
|
||||
internal interface IProjectLifecycle {
|
||||
|
||||
/**
|
||||
* 当 Gradle 开始装载项目时回调
|
||||
* @param project 当前项目
|
||||
*/
|
||||
fun onLoaded(project: Project)
|
||||
|
||||
/**
|
||||
* 当 Gradle 项目装载完成时回调
|
||||
* @param project 当前项目
|
||||
*/
|
||||
fun onEvaluate(project: Project)
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/10.
|
||||
*/
|
||||
package com.highcapable.flexilocale.plugin
|
||||
|
||||
import com.highcapable.flexilocale.FlexiLocale
|
||||
import com.highcapable.flexilocale.gradle.factory.get
|
||||
import com.highcapable.flexilocale.gradle.factory.getOrCreate
|
||||
import com.highcapable.flexilocale.gradle.proxy.IProjectLifecycle
|
||||
import com.highcapable.flexilocale.plugin.extension.dsl.configure.FlexiLocaleConfigureExtension
|
||||
import com.highcapable.flexilocale.plugin.helper.LocaleAnalysisHelper
|
||||
import com.highcapable.flexilocale.utils.debug.FError
|
||||
import org.gradle.api.Project
|
||||
|
||||
/**
|
||||
* [FlexiLocale] 插件扩展类
|
||||
*/
|
||||
internal class FlexiLocaleExtension internal constructor() : IProjectLifecycle {
|
||||
|
||||
private companion object {
|
||||
|
||||
/** Android Gradle plugin 扩展名称 */
|
||||
private const val ANDROID_EXTENSION_NAME = "android"
|
||||
}
|
||||
|
||||
/** 当前配置方法体实例 */
|
||||
private var configure: FlexiLocaleConfigureExtension? = null
|
||||
|
||||
override fun onLoaded(project: Project) {
|
||||
runCatching {
|
||||
configure = project.get(ANDROID_EXTENSION_NAME).getOrCreate<FlexiLocaleConfigureExtension>(FlexiLocaleConfigureExtension.NAME)
|
||||
}.onFailure { FError.make("Configure $project got an error, ${FlexiLocale.TAG} can only supports Android projects\nCaused by: $it") }
|
||||
}
|
||||
|
||||
override fun onEvaluate(project: Project) {
|
||||
val configs = configure?.build(project) ?: FError.make("Extension \"${FlexiLocaleConfigureExtension.NAME}\" create failed")
|
||||
LocaleAnalysisHelper.start(project, configs)
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/10.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.highcapable.flexilocale.plugin
|
||||
|
||||
import com.highcapable.flexilocale.FlexiLocale
|
||||
import com.highcapable.flexilocale.utils.debug.FError
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.ExtensionAware
|
||||
|
||||
/**
|
||||
* [FlexiLocale] 插件定义类
|
||||
*/
|
||||
class FlexiLocalePlugin<T : ExtensionAware> internal constructor() : Plugin<T> {
|
||||
|
||||
/** 当前扩展实例 */
|
||||
private val extension = FlexiLocaleExtension()
|
||||
|
||||
override fun apply(target: T) = when (target) {
|
||||
is Project -> {
|
||||
extension.onLoaded(target)
|
||||
target.afterEvaluate { extension.onEvaluate(project = this) }
|
||||
}
|
||||
else -> FError.make("${FlexiLocale.TAG} can only applied in build.gradle or build.gradle.kts, but current is $target")
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/11.
|
||||
*/
|
||||
package com.highcapable.flexilocale.plugin.config.proxy
|
||||
|
||||
import com.highcapable.flexilocale.FlexiLocale
|
||||
import com.highcapable.flexilocale.generated.FlexiLocaleProperties
|
||||
|
||||
/**
|
||||
* [FlexiLocale] 配置类接口类
|
||||
*/
|
||||
internal interface IFlexiLocaleConfigs {
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* 默认的生成目录路径
|
||||
*
|
||||
* "build/generated/[FlexiLocaleProperties.PROJECT_MODULE_NAME]"
|
||||
*/
|
||||
internal const val DEFAULT_GENERATE_DIR_PATH = "build/generated/${FlexiLocaleProperties.PROJECT_MODULE_NAME}"
|
||||
}
|
||||
|
||||
/** 是否启用插件 */
|
||||
val isEnable: Boolean
|
||||
|
||||
/** 自定义生成的目录路径 */
|
||||
val generateDirPath: String
|
||||
|
||||
/** 自定义生成的包名 */
|
||||
val packageName: String
|
||||
|
||||
/** 自定义生成的类名 */
|
||||
val className: String
|
||||
|
||||
/** 是否启用受限访问功能 */
|
||||
val isEnableRestrictedAccess: Boolean
|
||||
|
||||
/**
|
||||
* 获取内部 [hashCode]
|
||||
* @return [Int]
|
||||
*/
|
||||
fun innerHashCode() = "$isEnable$generateDirPath$packageName$className$isEnableRestrictedAccess".hashCode()
|
||||
}
|
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/11.
|
||||
*/
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.highcapable.flexilocale.plugin.extension.dsl.configure
|
||||
|
||||
import com.highcapable.flexilocale.FlexiLocale
|
||||
import com.highcapable.flexilocale.gradle.factory.fullName
|
||||
import com.highcapable.flexilocale.plugin.config.proxy.IFlexiLocaleConfigs
|
||||
import com.highcapable.flexilocale.utils.debug.FError
|
||||
import com.highcapable.flexilocale.utils.factory.uppercamelcase
|
||||
import org.gradle.api.Project
|
||||
|
||||
/**
|
||||
* [FlexiLocale] 配置方法体实现类
|
||||
*/
|
||||
open class FlexiLocaleConfigureExtension internal constructor() {
|
||||
|
||||
internal companion object {
|
||||
|
||||
/** [FlexiLocaleConfigureExtension] 扩展名称 */
|
||||
internal const val NAME = "flexiLocale"
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用插件
|
||||
*
|
||||
* 默认启用 - 如果你想关闭插件 - 在这里设置就可以了
|
||||
*/
|
||||
var isEnable = true
|
||||
@JvmName("enable") set
|
||||
|
||||
/**
|
||||
* 自定义生成的目录路径
|
||||
*
|
||||
* 你可以填写相对于当前项目的路径
|
||||
*
|
||||
* 默认为 [IFlexiLocaleConfigs.DEFAULT_GENERATE_DIR_PATH]
|
||||
*/
|
||||
var generateDirPath = IFlexiLocaleConfigs.DEFAULT_GENERATE_DIR_PATH
|
||||
@JvmName("generateDirPath") set
|
||||
|
||||
/**
|
||||
* 自定义生成的包名
|
||||
*
|
||||
* Android 项目默认使用 "android" 配置方法块中的 "namespace"
|
||||
*/
|
||||
var packageName = ""
|
||||
@JvmName("packageName") set
|
||||
|
||||
/**
|
||||
* 自定义生成的类名
|
||||
*
|
||||
* 默认使用当前项目的名称 + "Locale"
|
||||
*/
|
||||
var className = ""
|
||||
@JvmName("className") set
|
||||
|
||||
/**
|
||||
* 是否启用受限访问功能
|
||||
*
|
||||
* 默认不启用 - 启用后将为生成的类和方法添加 "internal" 修饰符
|
||||
*/
|
||||
var isEnableRestrictedAccess = false
|
||||
@JvmName("enableRestrictedAccess") set
|
||||
|
||||
/**
|
||||
* 构造 [IFlexiLocaleConfigs]
|
||||
* @param project 当前项目
|
||||
* @return [IFlexiLocaleConfigs]
|
||||
*/
|
||||
internal fun build(project: Project): IFlexiLocaleConfigs {
|
||||
/** 检查合法包名 */
|
||||
fun String.checkingValidPackageName() {
|
||||
if (isNotBlank() && !matches("^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*$".toRegex()))
|
||||
FError.make("Invalid package name \"$this\"")
|
||||
}
|
||||
|
||||
/** 检查合法类名 */
|
||||
fun String.checkingValidClassName() {
|
||||
if (isNotBlank() && !matches("^[a-zA-Z][a-zA-Z0-9_]*$".toRegex()))
|
||||
FError.make("Invalid class name \"$this\"")
|
||||
}
|
||||
packageName.checkingValidPackageName()
|
||||
className.checkingValidClassName()
|
||||
val currentEnable = isEnable
|
||||
val currentGenerateDirPath = project.file(generateDirPath).absolutePath
|
||||
val currentPackageName = packageName
|
||||
val currentClassName = "${className.ifBlank { project.fullName().uppercamelcase() }}Locale"
|
||||
val currentEnableRestrictedAccess = isEnableRestrictedAccess
|
||||
return object : IFlexiLocaleConfigs {
|
||||
override val isEnable get() = currentEnable
|
||||
override val generateDirPath get() = currentGenerateDirPath
|
||||
override val packageName get() = currentPackageName
|
||||
override val className get() = currentClassName
|
||||
override val isEnableRestrictedAccess get() = currentEnableRestrictedAccess
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/10.
|
||||
*/
|
||||
package com.highcapable.flexilocale.plugin.generator
|
||||
|
||||
import com.highcapable.flexilocale.FlexiLocale
|
||||
import com.highcapable.flexilocale.plugin.config.proxy.IFlexiLocaleConfigs
|
||||
import com.highcapable.flexilocale.plugin.generator.factory.LocaleStringMap
|
||||
import com.highcapable.flexilocale.utils.debug.FError
|
||||
import com.highcapable.flexilocale.utils.factory.camelcase
|
||||
import com.highcapable.flexilocale.utils.factory.uppercamelcase
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.FileSpec
|
||||
import com.squareup.kotlinpoet.FunSpec
|
||||
import com.squareup.kotlinpoet.KModifier
|
||||
import com.squareup.kotlinpoet.LambdaTypeName
|
||||
import com.squareup.kotlinpoet.ParameterSpec
|
||||
import com.squareup.kotlinpoet.PropertySpec
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
import com.squareup.kotlinpoet.asTypeName
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* I18ns 生成工具类
|
||||
*/
|
||||
internal class LocaleSourcesGenerator {
|
||||
|
||||
/**
|
||||
* 生成 [FileSpec]
|
||||
* @param configs 当前配置
|
||||
* @param keyValues 键值数组
|
||||
* @param namespace 命名空间
|
||||
* @param packageName 包名
|
||||
* @return [FileSpec]
|
||||
* @throws IllegalStateException 如果生成失败
|
||||
*/
|
||||
internal fun build(
|
||||
configs: IFlexiLocaleConfigs,
|
||||
keyValues: LocaleStringMap,
|
||||
namespace: String,
|
||||
packageName: String
|
||||
) = runCatching {
|
||||
FileSpec.builder(packageName, configs.className).apply {
|
||||
val selfClass = ClassName(packageName, configs.className)
|
||||
val contextClass = ClassName("android.content", "Context")
|
||||
val resourcesClass = ClassName("android.content.res", "Resources")
|
||||
val resourcesInitializer = LambdaTypeName.get(returnType = resourcesClass, parameters = emptyList())
|
||||
addAnnotation(AnnotationSpec.builder(Suppress::class).addMember("\"StringFormatInvalid\"").build())
|
||||
addImport(namespace, "R")
|
||||
addType(TypeSpec.classBuilder(selfClass).apply {
|
||||
addKdoc(
|
||||
"""
|
||||
This class is generated by ${FlexiLocale.TAG} at ${SimpleDateFormat.getDateTimeInstance().format(Date())}
|
||||
|
||||
The content here is automatically generated according to the res/values of your projects
|
||||
|
||||
You can visit [here](${FlexiLocale.PROJECT_URL}) for more help
|
||||
""".trimIndent()
|
||||
)
|
||||
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
|
||||
addFunction(FunSpec.constructorBuilder().addModifiers(KModifier.PRIVATE).build())
|
||||
addProperty(PropertySpec.builder("context", contextClass.copy(nullable = true)).apply {
|
||||
addKdoc("The current [Context] for this app or library")
|
||||
addModifiers(KModifier.PRIVATE)
|
||||
mutable()
|
||||
initializer("null")
|
||||
}.build())
|
||||
addProperty(PropertySpec.builder("resources", resourcesClass.copy(nullable = true)).apply {
|
||||
addKdoc("The current [Resources] for this app or library")
|
||||
addModifiers(KModifier.PRIVATE)
|
||||
mutable()
|
||||
initializer("null")
|
||||
}.build())
|
||||
addProperty(PropertySpec.builder("resourcesInitializer", resourcesInitializer.copy(nullable = true)).apply {
|
||||
addKdoc("The current [Resources] initializer for this app or library")
|
||||
addModifiers(KModifier.PRIVATE)
|
||||
mutable()
|
||||
initializer("null")
|
||||
}.build())
|
||||
addType(TypeSpec.companionObjectBuilder().apply {
|
||||
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
|
||||
addFunction(FunSpec.builder("attach").apply {
|
||||
addKdoc(
|
||||
"""
|
||||
Attach [${selfClass.simpleName}] to [Context]
|
||||
@param context like [android.app.Application] or [android.app.Activity]
|
||||
@return [${selfClass.simpleName}]
|
||||
""".trimIndent()
|
||||
)
|
||||
addAnnotation(JvmStatic::class)
|
||||
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
|
||||
addParameter("context", contextClass)
|
||||
addStatement("return ${selfClass.simpleName}().apply { this.context = context }")
|
||||
returns(selfClass)
|
||||
}.build())
|
||||
addFunction(FunSpec.builder("attach").apply {
|
||||
addKdoc(
|
||||
"""
|
||||
Attach [${selfClass.simpleName}] to [Resources]
|
||||
|
||||
- Note: this method will have no effect if [context] already exists
|
||||
@param resources A [Resources] that exists and has not been recycled
|
||||
@return [${selfClass.simpleName}]
|
||||
""".trimIndent()
|
||||
)
|
||||
addAnnotation(JvmStatic::class)
|
||||
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
|
||||
addParameter("resources", resourcesClass)
|
||||
addStatement("return ${selfClass.simpleName}().apply { this.resources = resources }")
|
||||
returns(selfClass)
|
||||
}.build())
|
||||
addFunction(FunSpec.builder("attach").apply {
|
||||
addKdoc(
|
||||
"""
|
||||
Attach [${selfClass.simpleName}] to [Resources] initializer
|
||||
|
||||
- Note: this method will have no effect if [context] already exists
|
||||
@param resourcesInitializer A [Resources] initializer returns a non-recycled instance
|
||||
@return [${selfClass.simpleName}]
|
||||
""".trimIndent()
|
||||
)
|
||||
addAnnotation(JvmStatic::class)
|
||||
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
|
||||
addParameter("resourcesInitializer", resourcesInitializer)
|
||||
addStatement("return ${selfClass.simpleName}().apply { this.resourcesInitializer = resourcesInitializer }")
|
||||
returns(selfClass)
|
||||
}.build())
|
||||
}.build())
|
||||
addProperty(PropertySpec.builder("currentResources", resourcesClass).apply {
|
||||
addKdoc("The current used [Resources] for this app or library")
|
||||
addModifiers(KModifier.PRIVATE)
|
||||
getter(FunSpec.getterBuilder().apply {
|
||||
addStatement("return context?.resources ?: resourcesInitializer?.invoke() ?: resources" +
|
||||
"?: error(\"${("Unable to get Resource instance, the app may have been killed " +
|
||||
"or initialization process failed").toKotlinPoetSpace()}\")")
|
||||
}.build())
|
||||
}.build())
|
||||
keyValues.forEach { (key, contentValues) ->
|
||||
val fixedKey = key.camelcase()
|
||||
val getterKey = "get${key.uppercamelcase()}"
|
||||
val statement = "return currentResources.getString(R.string.$key, *formatArgs)"
|
||||
var kDoc = "Resolve the [R.string.$key]\n\n"
|
||||
if (contentValues.isNotEmpty()) kDoc += "| Configuration | Value |\n| --- | --- |\n"
|
||||
contentValues.toList()
|
||||
.sortedWith(compareBy<Pair<String, String>> { it.first != "default" }.thenBy { it.first })
|
||||
.toAutoWrapKeyValues()
|
||||
.forEach { (key, value) ->
|
||||
val displayValue = value.replace("%".toRegex(), "%%")
|
||||
kDoc += "| $key | $displayValue |\n"
|
||||
}; kDoc = kDoc.trim()
|
||||
addProperty(PropertySpec.builder(fixedKey, String::class).apply {
|
||||
addKdoc(kDoc)
|
||||
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
|
||||
getter(FunSpec.getterBuilder().apply {
|
||||
addAnnotation(AnnotationSpec.builder(JvmName::class).addMember("\"$getterKey\"").build())
|
||||
addStatement("return $fixedKey()")
|
||||
}.build())
|
||||
}.build())
|
||||
addFunction(FunSpec.builder(fixedKey).apply {
|
||||
addKdoc("$kDoc\n@param formatArgs The format arguments that will be used for substitution")
|
||||
addAnnotation(AnnotationSpec.builder(JvmName::class).addMember("\"$getterKey\"").build())
|
||||
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
|
||||
addParameter(ParameterSpec.builder("formatArgs", Any::class.asTypeName()).addModifiers(KModifier.VARARG).build())
|
||||
addStatement(statement)
|
||||
returns(String::class)
|
||||
}.build())
|
||||
}
|
||||
}.build())
|
||||
}.build()
|
||||
}.getOrElse { FError.make("Failed to generated Kotlin file\n$it") }
|
||||
|
||||
/**
|
||||
* 转换为自动换行键值对数组
|
||||
* @return [List]<[Pair]<[String], [String]>>
|
||||
*/
|
||||
private fun List<Pair<String, String>>.toAutoWrapKeyValues(): List<Pair<String, String>> {
|
||||
val maxAllowLength = 75
|
||||
val punctuations = charArrayOf('.', '。', ',', ',', '、', ';', ';', ':', ':', '!', '!', '?', '?')
|
||||
val result = mutableListOf<Pair<String, String>>()
|
||||
val placeholders = mutableListOf<Pair<String, String>>()
|
||||
forEach {
|
||||
var key = it.first
|
||||
var value = it.second.replace("\\n", "ㅤ")
|
||||
val maxLength = abs(maxAllowLength - key.length)
|
||||
while (value.length > maxLength) {
|
||||
var splitIndex = maxLength
|
||||
var splitValue = value.substring(0, splitIndex)
|
||||
val lastSpaceIndex = splitValue.lastIndexOf(' ')
|
||||
val lastPunctuationIndex = splitValue.lastIndexOfAny(punctuations)
|
||||
val hashWrapIndex = splitValue.lastIndexOf('ㅤ')
|
||||
when {
|
||||
hashWrapIndex != -1 && (hashWrapIndex < lastSpaceIndex || hashWrapIndex < lastPunctuationIndex) -> {
|
||||
splitIndex = hashWrapIndex
|
||||
splitValue = value.substring(0, splitIndex)
|
||||
}
|
||||
lastSpaceIndex != -1 && lastSpaceIndex >= lastPunctuationIndex -> {
|
||||
splitIndex = lastSpaceIndex + 1
|
||||
splitValue = value.substring(0, splitIndex)
|
||||
}
|
||||
lastPunctuationIndex != -1 -> {
|
||||
splitIndex = lastPunctuationIndex + 1
|
||||
splitValue = value.substring(0, splitIndex)
|
||||
}
|
||||
}
|
||||
value = value.substring(splitIndex).trimStart('ㅤ')
|
||||
result.add(key to splitValue)
|
||||
key = " ".repeat(key.length)
|
||||
}
|
||||
if (value.isNotEmpty())
|
||||
result.add(key to value.replace("ㅤ", ""))
|
||||
else placeholders.add(key to "")
|
||||
}; result.addAll(placeholders)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换到 KotlinPoet 声明的空格
|
||||
* @return [String]
|
||||
*/
|
||||
private fun String.toKotlinPoetSpace() = replace(" ", "·")
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/13.
|
||||
*/
|
||||
package com.highcapable.flexilocale.plugin.generator.factory
|
||||
|
||||
import java.io.File
|
||||
|
||||
/** I18ns 数组类型定义 */
|
||||
internal typealias LocaleStringMap = MutableMap<String, LocaleChildMap>
|
||||
|
||||
/** I18ns (子键值对) 数组类型定义 */
|
||||
internal typealias LocaleChildMap = MutableMap<String, String>
|
||||
|
||||
/** I18ns (文件) 数组类型定义 */
|
||||
internal typealias LocaleFileMap = MutableMap<String, MutableSet<File>>
|
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/10.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.highcapable.flexilocale.plugin.helper
|
||||
|
||||
import com.android.build.gradle.AppExtension
|
||||
import com.android.build.gradle.BaseExtension
|
||||
import com.android.build.gradle.LibraryExtension
|
||||
import com.android.build.gradle.api.BaseVariant
|
||||
import com.highcapable.flexilocale.gradle.factory.get
|
||||
import com.highcapable.flexilocale.plugin.config.proxy.IFlexiLocaleConfigs
|
||||
import com.highcapable.flexilocale.plugin.generator.LocaleSourcesGenerator
|
||||
import com.highcapable.flexilocale.plugin.generator.factory.LocaleChildMap
|
||||
import com.highcapable.flexilocale.plugin.generator.factory.LocaleFileMap
|
||||
import com.highcapable.flexilocale.plugin.generator.factory.LocaleStringMap
|
||||
import com.highcapable.flexilocale.utils.debug.FError
|
||||
import com.highcapable.flexilocale.utils.debug.FLog
|
||||
import com.highcapable.flexilocale.utils.factory.toFile
|
||||
import org.gradle.api.Project
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.Node
|
||||
import java.io.File
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
|
||||
/**
|
||||
* I18ns 分析工具类
|
||||
*/
|
||||
internal object LocaleAnalysisHelper {
|
||||
|
||||
/** Android 的 Application 插件名称 */
|
||||
private const val APPLICATION_PLUGIN_NAME = "com.android.application"
|
||||
|
||||
/** Android 的 Library 插件名称 */
|
||||
private const val LIBRARY_PLUGIN_NAME = "com.android.library"
|
||||
|
||||
/** Kotlin 的 Android 插件名称 */
|
||||
private const val KT_ANDROID_PLUGIN_NAME = "org.jetbrains.kotlin.android"
|
||||
|
||||
/** I18ns 代码生成实例 */
|
||||
private val generator = LocaleSourcesGenerator()
|
||||
|
||||
/** 当前全部 I18ns 数据 (来自但不一定完全为 strings.xml) */
|
||||
private val mappedStrings: LocaleStringMap = mutableMapOf()
|
||||
|
||||
/** 当前项目命名空间 */
|
||||
private var namespace = ""
|
||||
|
||||
/** 当前项目资源目录数组 */
|
||||
private val resDirectories = mutableListOf<File>()
|
||||
|
||||
/** 上次修改的 Hash Code */
|
||||
private var lastModifiedHashCode = 0
|
||||
|
||||
/** 配置是否已被修改 */
|
||||
private var isConfigsModified = true
|
||||
|
||||
/** 当前使用的配置实例 */
|
||||
private lateinit var configs: IFlexiLocaleConfigs
|
||||
|
||||
/**
|
||||
* 开始分析当前项目
|
||||
* @param project 当前项目
|
||||
* @param configs 当前配置
|
||||
*/
|
||||
internal fun start(project: Project, configs: IFlexiLocaleConfigs) {
|
||||
this.configs = configs
|
||||
if (!configs.isEnable) return
|
||||
checkingConfigsModified(project, configs)
|
||||
initializePlugins(project)
|
||||
val lastMappedStrings: LocaleStringMap = mutableMapOf()
|
||||
val lastResolveStrings: LocaleStringMap = mutableMapOf()
|
||||
resDirectories.takeIf { it.isNotEmpty() }?.allValuesDirs()?.forEach { (localeName, files) ->
|
||||
val stringXmls: LocaleChildMap = mutableMapOf()
|
||||
files.forEach { stringXmls.putAll(resolveStringXml(it)) }
|
||||
lastResolveStrings[localeName] = stringXmls
|
||||
} ?: return FLog.warn(
|
||||
"Unable to get the resources dir of $project, " +
|
||||
"please check whether there does not have a resources dir or is not an Android project"
|
||||
)
|
||||
lastResolveStrings.onEach { (localeName, strings) ->
|
||||
strings.forEach { (key, value) ->
|
||||
if (lastMappedStrings[key] == null) lastMappedStrings[key] = mutableMapOf()
|
||||
lastMappedStrings[key]?.set(localeName, value)
|
||||
}
|
||||
}.clear()
|
||||
val isFileModified = mappedStrings != lastMappedStrings
|
||||
if (!isFileModified && !isConfigsModified) return
|
||||
mappedStrings.clear()
|
||||
mappedStrings.putAll(lastMappedStrings)
|
||||
lastMappedStrings.clear()
|
||||
updateGeneration()
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查配置是否已被修改
|
||||
* @param project 当前项目
|
||||
* @param configs 当前配置
|
||||
*/
|
||||
private fun checkingConfigsModified(project: Project, configs: IFlexiLocaleConfigs) {
|
||||
val fileHashCode = project.buildFile.takeIf { it.exists() }?.readText()?.hashCode() ?: -1
|
||||
isConfigsModified = fileHashCode == -1 || lastModifiedHashCode != fileHashCode || this.configs.innerHashCode() != configs.innerHashCode()
|
||||
lastModifiedHashCode = fileHashCode
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 Android Gradle plugin
|
||||
* @param project 当前项目
|
||||
*/
|
||||
private fun initializePlugins(project: Project) {
|
||||
runCatching {
|
||||
fun BaseExtension.updateSourceDirs() = sourceSets.configureEach { kotlin.srcDir(configs.generateDirPath) }
|
||||
fun KotlinProjectExtension.updateSourceDirs() = sourceSets.configureEach { kotlin.srcDir(configs.generateDirPath) }
|
||||
fun BaseVariant.updateResDirectories() = sourceSets.forEach { provide -> provide.resDirectories?.also { resDirectories.addAll(it) } }
|
||||
project.plugins.withId(APPLICATION_PLUGIN_NAME) {
|
||||
project.get<AppExtension>().also { extension ->
|
||||
namespace = extension.namespace ?: ""
|
||||
extension.applicationVariants.forEach { variant ->
|
||||
variant.updateResDirectories()
|
||||
}; extension.updateSourceDirs()
|
||||
}
|
||||
}
|
||||
project.plugins.withId(LIBRARY_PLUGIN_NAME) {
|
||||
project.get<LibraryExtension>().also { extension ->
|
||||
namespace = extension.namespace ?: ""
|
||||
extension.libraryVariants.forEach { variant ->
|
||||
variant.updateResDirectories()
|
||||
}; extension.updateSourceDirs()
|
||||
}
|
||||
}
|
||||
project.plugins.withId(KT_ANDROID_PLUGIN_NAME) {
|
||||
project.get<KotlinAndroidProjectExtension>().also { extension ->
|
||||
extension.updateSourceDirs()
|
||||
}
|
||||
}
|
||||
}.onFailure { FError.make("Failed to initialize Android Gradle plugin, this may be not or a wrong Android project\n$it") }
|
||||
}
|
||||
|
||||
/** 更新生成后的代码内容 */
|
||||
private fun updateGeneration() {
|
||||
val packageName = "${configs.packageName.ifBlank { namespace }}.generated.locale"
|
||||
val generateDir = configs.generateDirPath.toFile().apply { if (exists() && isDirectory) deleteRecursively() }
|
||||
generator.build(configs, mappedStrings, namespace, packageName).writeTo(generateDir)
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前资源目录下的全部可用 values 目录数组 (包含 I18ns 数据)
|
||||
* @return [LocaleFileMap]
|
||||
*/
|
||||
private fun List<File>.allValuesDirs(): LocaleFileMap {
|
||||
val valuesDirs: LocaleFileMap = mutableMapOf()
|
||||
forEach {
|
||||
it.listFiles()?.filter { dir -> dir.name.startsWith("values") }?.forEach eachDir@{ valuesDir ->
|
||||
if (!valuesDir.exists() || !valuesDir.isDirectory) return@eachDir
|
||||
val langName = if (valuesDir.name == "values") "default" else valuesDir.name.split("s-").getOrNull(1) ?: return@eachDir
|
||||
if (valuesDirs[langName] == null) valuesDirs[langName] = mutableSetOf()
|
||||
valuesDirs[langName]?.add(valuesDir)
|
||||
}
|
||||
}; return valuesDirs
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前资源目录下的全部 Xml 文件内容到键值对数组
|
||||
* @param valuesDir 当前资源目录
|
||||
* @return [LocaleChildMap]
|
||||
*/
|
||||
private fun resolveStringXml(valuesDir: File): LocaleChildMap {
|
||||
val lastMappedStrings: LocaleChildMap = mutableMapOf()
|
||||
valuesDir.listFiles()?.filter { it.name.endsWith(".xml") }?.forEach {
|
||||
lastMappedStrings.putAll(it.readText().parseResourcesXml())
|
||||
}; return lastMappedStrings
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析资源 Xml 文件内容到键值对数组
|
||||
* @return [LocaleChildMap]
|
||||
*/
|
||||
private fun String.parseResourcesXml(): LocaleChildMap {
|
||||
val builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||
val document = runCatching { builder.parse(byteInputStream()) }.getOrNull() ?: return mutableMapOf()
|
||||
val rootNode = document.documentElement
|
||||
if (rootNode.nodeName != "resources") return mutableMapOf()
|
||||
val nodes = rootNode.getElementsByTagName("string")
|
||||
val keyValues: LocaleChildMap = mutableMapOf()
|
||||
(0 until nodes.length).forEach { index ->
|
||||
val node = nodes.item(index)
|
||||
if (node.nodeType == Node.ELEMENT_NODE) {
|
||||
val element = node as Element
|
||||
val name = element.getAttribute("name")
|
||||
val content = element.textContent
|
||||
keyValues[name] = content
|
||||
}
|
||||
}; return keyValues
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/10.
|
||||
*/
|
||||
package com.highcapable.flexilocale.utils.debug
|
||||
|
||||
import com.highcapable.flexilocale.FlexiLocale
|
||||
|
||||
/**
|
||||
* 全局异常管理类
|
||||
*/
|
||||
internal object FError {
|
||||
|
||||
/**
|
||||
* 抛出异常
|
||||
* @param msg 消息内容
|
||||
* @throws IllegalStateException
|
||||
*/
|
||||
internal fun make(msg: String): Nothing = error("[${FlexiLocale.TAG}] $msg")
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/10.
|
||||
*/
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.highcapable.flexilocale.utils.debug
|
||||
|
||||
import com.highcapable.flexilocale.FlexiLocale
|
||||
import org.apache.log4j.Logger
|
||||
|
||||
/**
|
||||
* 全局 Log 管理类
|
||||
*/
|
||||
internal object FLog {
|
||||
|
||||
internal const val DONE = "✅"
|
||||
internal const val IGNORE = "❎"
|
||||
internal const val ERROR = "❌"
|
||||
internal const val WARN = "⚠️"
|
||||
internal const val LINK = "➡️"
|
||||
internal const val WIRE = "⚙️"
|
||||
internal const val UP = "⬆️"
|
||||
internal const val ROTATE = "\uD83D\uDD04"
|
||||
internal const val ANLZE = "\uD83D\uDD0D"
|
||||
internal const val STRNG = "\uD83D\uDCAA"
|
||||
|
||||
/** 当前日志输出对象 */
|
||||
private val logger = Logger.getLogger(FLog::class.java)
|
||||
|
||||
/**
|
||||
* 打印 Info (提醒) 级别 Log (绿色)
|
||||
* @param msg 消息内容
|
||||
* @param symbol 前缀符号 - 仅限非 [noTag] - 默认无
|
||||
* @param noTag 无标签 - 默认否
|
||||
*/
|
||||
internal fun note(msg: Any, symbol: String = "", noTag: Boolean = false) =
|
||||
log(if (noTag) msg else msg.createSymbolMsg(symbol), color = "38;5;10")
|
||||
|
||||
/**
|
||||
* 打印 Info 级别 Log (无颜色)
|
||||
* @param msg 消息内容
|
||||
* @param symbol 前缀符号 - 仅限非 [noTag] - 默认无
|
||||
* @param noTag 无标签 - 默认否
|
||||
*/
|
||||
internal fun info(msg: Any, symbol: String = "", noTag: Boolean = false) =
|
||||
log(if (noTag) msg else msg.createSymbolMsg(symbol))
|
||||
|
||||
/**
|
||||
* 打印 Warn 级别 Log (黄色)
|
||||
* @param msg 消息内容
|
||||
* @param symbol 前缀符号 - 仅限非 [noTag] - 默认 [WARN]
|
||||
* @param noTag 无标签 - 默认否
|
||||
*/
|
||||
internal fun warn(msg: Any, symbol: String = WARN, noTag: Boolean = false) =
|
||||
log(if (noTag) msg else msg.createSymbolMsg(symbol), color = "33")
|
||||
|
||||
/**
|
||||
* 打印 Error 级别 Log (红色)
|
||||
* @param msg 消息内容
|
||||
* @param symbol 前缀符号 - 仅限非 [noTag] - 默认 [ERROR]
|
||||
* @param noTag 无标签 - 默认否
|
||||
*/
|
||||
internal fun error(msg: Any, symbol: String = ERROR, noTag: Boolean = false) =
|
||||
log(if (noTag) msg else msg.createSymbolMsg(symbol), isError = true)
|
||||
|
||||
/**
|
||||
* 创建符号消息内容
|
||||
* @param symbol 前缀符号
|
||||
* @return [String]
|
||||
*/
|
||||
private fun Any.createSymbolMsg(symbol: String) =
|
||||
if (symbol.isNotBlank()) "[${FlexiLocale.TAG}] $symbol $this" else "[${FlexiLocale.TAG}] $this"
|
||||
|
||||
/**
|
||||
* 打印 Log
|
||||
* @param msg 消息内容
|
||||
* @param color 颜色代码 - 默认无颜色
|
||||
* @param isError 是否强制为错误日志 - 默认否
|
||||
*/
|
||||
private fun log(msg: Any, color: String = "0", isError: Boolean = false) = when {
|
||||
isError -> logger.error(msg)
|
||||
color != "0" -> println("\u001B[${color}m$msg\u001B[0m")
|
||||
else -> println(msg)
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/11.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.highcapable.flexilocale.utils.factory
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* 字符串路径转换为文件
|
||||
*
|
||||
* 自动调用 [parseFileSeparator]
|
||||
* @return [File]
|
||||
*/
|
||||
internal fun String.toFile() = File(parseFileSeparator())
|
||||
|
||||
/**
|
||||
* 格式化到当前操作系统的文件分隔符
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.parseFileSeparator() = replace("/", File.separator).replace("\\", File.separator)
|
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
|
||||
* Copyright (C) 2019-2023 HighCapable
|
||||
* https://github.com/BetterAndroid/FlexiLocale
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*
|
||||
* This file is created by fankes on 2023/10/10.
|
||||
*/
|
||||
package com.highcapable.flexilocale.utils.factory
|
||||
|
||||
/**
|
||||
* 下划线、分隔线、点、冒号、空格命名字符串转小驼峰命名字符串
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.camelcase() = runCatching {
|
||||
split("_", ".", "-", ":", " ").map { it.replaceFirstChar { e -> e.titlecase() } }.let { words ->
|
||||
words.first().replaceFirstChar { it.lowercase() } + words.drop(1).joinToString("")
|
||||
}
|
||||
}.getOrNull() ?: this
|
||||
|
||||
/**
|
||||
* 下划线、分隔线、点、空格命名字符串转大驼峰命名字符串
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.uppercamelcase() = camelcase().capitalize()
|
||||
|
||||
/**
|
||||
* 字符串首字母大写
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.capitalize() = replaceFirstChar { it.uppercaseChar() }
|
Reference in New Issue
Block a user