commit 78217514bf936f775bb90b2050ac86ebdca4216b Author: fankesyooni Date: Fri Oct 13 17:42:25 2023 +0800 Initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7101007 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# noinspection EditorConfigKeyCorrectness +[{*.kt,*.kts}] +ktlint_standard_annotation = disabled +ktlint_standard_filename = disabled +ktlint_standard_wrapping = disabled +ktlint_standard_import-ordering = enabled +ktlint_standard_max-line-length = disabled +ktlint_standard_multiline-if-else = disabled +ktlint_standard_argument-list-wrapping = disabled +ktlint_standard_parameter-list-wrapping = disabled +ktlint_standard_trailing-comma-on-declaration-site = disabled +ktlint_function_signature_body_expression_wrapping = multiline +ij_continuation_indent_size = 2 +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 150 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b63da45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..d649463 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +/gradle.xml +/misc.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml \ No newline at end of file diff --git a/.idea/icon.png b/.idea/icon.png new file mode 100644 index 0000000..0c3d6c1 Binary files /dev/null and b/.idea/icon.png differ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..bc2cda4 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..f8467b4 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/ktlint.xml b/.idea/ktlint.xml new file mode 100644 index 0000000..92c4441 --- /dev/null +++ b/.idea/ktlint.xml @@ -0,0 +1,6 @@ + + + + false + + \ No newline at end of file diff --git a/.idea/markdown.xml b/.idea/markdown.xml new file mode 100644 index 0000000..f6d2542 --- /dev/null +++ b/.idea/markdown.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..80ba5d5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright HighCapable [name of copyright owner] + + 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. diff --git a/README-zh-CN.md b/README-zh-CN.md new file mode 100644 index 0000000..6a9faae --- /dev/null +++ b/README-zh-CN.md @@ -0,0 +1,100 @@ +# Flexi Locale + +[![GitHub license](https://img.shields.io/github/license/BetterAndroid/FlexiLocale?color=blue)](https://github.com/BetterAndroid/FlexiLocale/blob/master/LICENSE) +[![GitHub release](https://img.shields.io/github/v/release/BetterAndroid/FlexiLocale?display_name=release&logo=github&color=green)](https://github.com/BetterAndroid/FlexiLocale/releases) +[![Telegram](https://img.shields.io/badge/discussion-Telegram-blue.svg?logo=telegram)](https://t.me/BetterAndroid) +[![Telegram](https://img.shields.io/badge/discussion%20dev-Telegram-blue.svg?logo=telegram)](https://t.me/HighCapable_Dev) + +LOGO + +一个自动为 Android 项目生成国际化字符串调用的 Gradle 插件。 + +[English](https://github.com/BetterAndroid/FlexiLocale/blob/master/README.md) | 简体中文 + +## 这是什么 + +这是一个用来自动为 Android 项目生成国际化字符串调用代码功能的 Gradle 插件。 + +在 Android 项目中,要使用国际化字符串,需要在 `strings.xml` 中进行定义,然后使用 `context.getString(R.string.xxx)` 的方式去调用,非常的繁琐和不灵活。 + +这就是这个项目诞生的原因,通过这个插件,你现在只需要实例化一次插件生成的 `AppLocale` 类,然后就可以在任意地方使用了。 + +> 传统写法 + +```kotlin +val appName = context.getString(R.string.app_name) +``` + +> 现代写法 + +```kotlin +val locale by lazy { AppLocale.attach(context) } +val appName = locale.appName +``` + +如果你依然在使用 Java,那么写法保持不变。 + +```java +var locale = AppLocale.attach(context); +var appName = locale.getAppName(); +``` + +## 兼容性 + +理论支持不是很旧的 Gradle,建议版本为 `7.x.x` 及以上。 + +支持包含 Kotlin 插件的 Android 项目,其它类型的项目暂不支持。 + +> 构建脚本语言 + +- Kotlin DSL + +推荐优先使用此语言作为构建脚本语言,这也是目前 Gradle 推荐的语言。 + +- Groovy DSL + +部分功能可能无法兼容,在后期会逐渐放弃支持,且部分功能会无法使用。 + +## 开始使用 + +- [点击这里](https://github.com/BetterAndroid/FlexiLocale/blob/master/docs/guide.md) 查看使用文档 + +## 更新日志 + +- [点击这里](https://github.com/BetterAndroid/FlexiLocale/blob/master/docs/changelog.md) 查看历史更新日志 + +## 项目推广 + +如果你正在寻找一个可以自动管理 Gradle 项目依赖的 Gradle 插件,你可以了解一下 [SweetDependency](https://github.com/HighCapable/SweetDependency) 项目。 + +如果你正在寻找一个可以自动生成属性键值的 Gradle 插件,你可以了解一下 [SweetProperty](https://github.com/HighCapable/SweetProperty) 项目。 + +本项目同样使用了 **SweetDependency** 和 **SweetProperty**。 + +## Star History + +![Star History Chart](https://api.star-history.com/svg?repos=BetterAndroid/FlexiLocale&type=Date) + +## 许可证 + +- [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +``` +Apache License Version 2.0 + +Copyright (C) 2019-2023 HighCapable + +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. +``` + +版权所有 © 2019-2023 HighCapable \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6a4ffc8 --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# Flexi Locale + +[![GitHub license](https://img.shields.io/github/license/BetterAndroid/FlexiLocale?color=blue)](https://github.com/BetterAndroid/FlexiLocale/blob/master/LICENSE) +[![GitHub release](https://img.shields.io/github/v/release/BetterAndroid/FlexiLocale?display_name=release&logo=github&color=green)](https://github.com/BetterAndroid/FlexiLocale/releases) +[![Telegram](https://img.shields.io/badge/discussion-Telegram-blue.svg?logo=telegram)](https://t.me/BetterAndroid) +[![Telegram](https://img.shields.io/badge/discussion%20dev-Telegram-blue.svg?logo=telegram)](https://t.me/HighCapable_Dev) + +LOGO + +An easy generation Android i18ns string call Gradle plugin. + +English | [简体中文](https://github.com/BetterAndroid/FlexiLocale/blob/master/README-zh-CN.md) + +## What's this + +This is a Gradle plugin for automatically generating i18ns string calling code functions for Android projects. + +In Android projects, to use i18ns string, you need to define them in `strings.xml` and then call them using `context.getString(R.string.xxx)`, which +is very cumbersome and inflexible. + +That's why this project was born. + +With this plugin, you now only need to instantiate the `AppLocale` class generated by the plugin once, and then you can use it anywhere. + +> Traditional Style + +```kotlin +val appName = context.getString(R.string.app_name) +``` + +> Modern Style + +```kotlin +val locale by lazy { AppLocale.attach(context) } +val appName = locale.appName +``` + +If you are still using Java, the writing method remains the same. + +```java +var locale=AppLocale.attach(context); + var appName=locale.getAppName(); +``` + +## Compatibility + +The theory supports not very old Gradle, the recommended version is `7.x.x` and above. + +Android projects containing Kotlin plugins are supported, other types of projects are not supported yet. + +> Build Script Language + +- Kotlin DSL + +It is recommended to use this language as the build script language first, which is also the language currently recommended by Gradle. + +- Groovy DSL + +Some functions may be incompatible, support will be gradually dropped in the future, and some functions may become unavailable. + +## Get Started + +- [Click here](https://github.com/BetterAndroid/FlexiLocale/blob/master/docs/guide.md) to view the documentation + +## Changelog + +- [Click here](https://github.com/BetterAndroid/FlexiLocale/blob/master/docs/changelog.md) to view the historical changelog + +## Promotion + +If you are looking for a Gradle plugin that can automatically manage Gradle project dependencies, +you can check out the [SweetDependency](https://github.com/HighCapable/SweetDependency) project. + +If you are looking for a Gradle plugin that can automatically generate properties key-values, +you can check out the [SweetProperty](https://github.com/HighCapable/SweetProperty) project. + +This project also uses **SweetDependency** and **SweetProperty**. + +## Star History + +![Star History Chart](https://api.star-history.com/svg?repos=BetterAndroid/FlexiLocale&type=Date) + +## License + +- [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +``` +Apache License Version 2.0 + +Copyright (C) 2019-2023 HighCapable + +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. +``` + +Copyright © 2019-2023 HighCapable \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..64ba158 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + autowire(libs.plugins.kotlin.jvm) apply false +} + +allprojects { + tasks.withType().configureEach { + kotlinOptions { + jvmTarget = "17" + freeCompilerArgs = listOf( + "-Xno-param-assertions", + "-Xno-call-assertions", + "-Xno-receiver-assertions" + ) + } + } +} \ No newline at end of file diff --git a/docs/changelog-zh-CN.md b/docs/changelog-zh-CN.md new file mode 100644 index 0000000..0204074 --- /dev/null +++ b/docs/changelog-zh-CN.md @@ -0,0 +1,5 @@ +# 更新日志 + +## 1.0.0 | 2023.10.13 + +- 首个版本提交至 Maven \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..72d7281 --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,5 @@ +# Changelog + +## 1.0.0 | 2023.10.13 + +- The first version is submitted to Maven \ No newline at end of file diff --git a/docs/guide-zh-CN.md b/docs/guide-zh-CN.md new file mode 100644 index 0000000..e56384d --- /dev/null +++ b/docs/guide-zh-CN.md @@ -0,0 +1,169 @@ +# Flexi Locale 使用文档 + +在开始使用之前,建议你仔细阅读此文档,以便你能更好地了解它的作用方式与功能。 + +如果你的项目依然在使用 Groovy DSL 进行管理,推荐迁移到 Kotlin DSL。 + +在 Groovy DSL 中使用此插件发生的任何问题,我们都将不再负责排查和修复,并且在后期版本可能会完全不再支持 Groovy DSL。 + +注意:此文档中将不再详细介绍在 Groovy DSL 中的使用方法。 + +## 快速开始 + +我们推荐使用 [SweetDependency](https://github.com/HighCapable/SweetDependency) 来自动管理依赖版本。 + +以下是使用 `SweetDependency` 的装载方式。 + +> 配置文件 + +```yaml +plugins: + com.highcapable.flexilocale: + alias: flexi-locale + version: + +``` + +> build.gradle.kts + +```kotlin +plugins { + // 装载方式 1 + autowire(libs.plugins.flexi.locale) + // 装载方式 2 + autowire("flexi-locale") +} +``` + +以下是传统的装载方式。 + +打开你需要集成 `FlexiLocale` 插件项目的 `build.gradle.kts`。 + +> 示例如下 + +```kotlin +plugins { + id("com.highcapable.flexilocale") version "" +} +``` + +请将上述代码中的 `` 替换为 [Release](https://github.com/BetterAndroid/FlexiLocale/releases) 中的最新版本, 请注意不要在后方加入 apply false。 + +上述配置完成后,运行一次 Gradle Sync。 + +不出意外的情况下,你将会得到自动生成的 `AppLocale` 类,`Locale` 前方的名称为你的项目名称,默认应该为 `App`。 + +## 功能配置 + +你可以对 `FlexiLocale` 进行配置来实现自定义和个性化功能。 + +`FlexiLocale` 为你提供了相对丰富的可自定义功能,下面是这些功能的说明与配置方法。 + +请在你的 `build.gradle.kts` 中添加 `flexiLocale` 方法块以开始配置 `FlexiLocale`。 + +`FlexiLocale` 依附于 `android` 方法块生成。 + +> 示例如下 + +```kotlin +android { + flexiLocale { + // 启用 FlexiLocale,设置为 false 将禁用所有功能 + isEnable = true + // 自定义生成的目录路径 + // 你可以填写相对于当前项目的路径 + // 默认为 "build/generated/flexi-locale" + // 建议将生成的代码放置于 "build" 目录下,因为生成的代码不建议去修改它 + generateDirPath = "build/generated/flexi-locale" + // 自定义生成的包名 + // Android 项目默认使用 "android" 配置方法块中的 "namespace" + // 你可以不进行设置,包名在一般情况下会自动进行匹配 + packageName = "com.example.mydemo" + // 自定义生成的类名 + // 默认使用当前项目的名称 + "Locale" + // 你可以不进行设置,类名在一般情况下会自动进行匹配 + className = "MyDemo" + // 是否启用受限访问功能 + // 默认不启用,启用后将为生成的类和方法添加 "internal" 修饰符 + // 如果你的项目为工具库或依赖,通常情况下建议启用 + // 启用后可以防止其他开发者在引用你的库时调用到你的项目国际化字符串调用类发生问题 + isEnableRestrictedAccess = false + } +} +``` + +如需在 Groovy DSL 中使用,请将所有变量的 `=` 改为空格,并删除 `Enable` 前方的 `is` 并将 `E` 小写即可。 + +## 使用示例 + +`FlexiLocale` 会自动扫描当前项目 `res/values` 目录中所有包含 `...` 的 XML 文件。 + +假设这是你当前项目的 `strings.xml`,分为 `default` 和 `zh-rCN` 两个目录。 + +> values/strings.xml + +```xml + + My App + Hello %1$s + +``` + +> values-zh-rCN/strings.xml + +```xml + + 我的应用 + 你好 %1$s + +``` + +我们建议在 `Application` 中装载 `AppLocale`,这样你就可以在应用的任何地方进行调用。 + +> 示例如下 + +```kotlin +lateinit var locale: AppLocale + +class App : Application() { + + override fun onCreate() { + locale = AppLocale.attach(this) + } +} +``` + +下面,你就可以直接去使用这些字符串了。 + +> 示例如下 + +```kotlin +class MainActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + // 将设置标题为: + // default: My App + // zh-rCN: 我的应用 + actionBar.title = locale.appName + // 将设置文本为: + // default: Hello John + // zh-rCN: 你好 John + findViewById(R.id.hello_text).text = locale.sayHello("John") + } +} +``` + +Java 的使用方式只需要将调用的字符串方法名前加入 `get` 即可。 + +需要注意的是,当你修改了 `strings.xml` 等资源文件,你需要重新运行一次 Gradle Sync 以得到最新的生成结果。 + +### 动态刷新 + +如果用户动态地修改了系统语言,你可以使用 `AppLocale` 中的 `attach { dynamicResources }` 方法设置动态资源实例。 + +如果你是使用 `Context` 装载的 `AppLocale`,那么你无需进行任何操作。 + +请注意,如果 `Activity` 未自动重新启动,请在 `Activity` 中手动调用 `recreate` 才能看到语言改变后的结果。 + +## 问题反馈 + +如果你在使用 `FlexiLocale` 的过程中遇到了任何问题,你都可以随时在 GitHub 开启一个 `issues` 向我们反馈。 \ No newline at end of file diff --git a/docs/guide.md b/docs/guide.md new file mode 100644 index 0000000..60ce0e3 --- /dev/null +++ b/docs/guide.md @@ -0,0 +1,176 @@ +# Flexi Locale Documentation + +Before you start using it, it is recommended that you read this document carefully so that you can better understand how it works and its functions. + +If your project is still managed using Groovy DSL, it is recommended to migrate to Kotlin DSL. + +We will no longer be responsible for troubleshooting and fixing any issues that occur with this plugin in Groovy DSL, and Groovy DSL support may be +dropped entirely in later releases. + +Note: Usage in the Groovy DSL will not be detailed in this document. + +## Quick Start + +We recommend using [SweetDependency](https://github.com/HighCapable/SweetDependency) to autowire dependencies versions. + +The following is the loading method using `SweetDependency`. + +> Configuration File + +```yaml +plugins: + com.highcapable.flexilocale: + alias: flexi-locale + version: + +``` + +> build.gradle.kts + +```kotlin +plugins { + // Loading method 1 + autowire(libs.plugins.flexi.locale) + // Loading method 2 + autowire("flexi-locale") +} +``` + +The following is the traditional loading method. + +Open the `build.gradle.kts` project where you need to integrate the `FlexiLocale` plugin. + +> The following example + +```kotlin +plugins { + id("com.highcapable.flexilocale") version "" +} +``` + +Please replace `` in the above code with the latest version in [Release](https://github.com/BetterAndroid/FlexiLocale/releases). + +Please be careful not to add apply false at the end. + +After the above configuration is completed, run Gradle Sync once. + +If nothing else goes wrong, you will get the automatically generated `AppLocale` class, +the name in front of `Locale` is your project name, and the default should be `App`. + +## Function Configuration + +You can configure `FlexiLocale` to achieve customization and personalization. + +`FlexiLocale` provides you with a relatively rich set of customizable functions. + +The following is the description and configuration method of these functions. + +Please add the `flexiLocale` method block to your `build.gradle.kts` to start configuring `FlexiLocale`. + +`FlexiLocale` depends on the `android` method block generation. + +> The following example + +```kotlin +android { + flexiLocale { + // Enable FlexiLocale, setting to false will disable all functionality + isEnable = true + // Customize the generated directory path + // You can fill in the path relative to the current project + // Default is "build/generated/flexi-locale" + // It is recommended to place the generated code in the "build" directory, because the generated code is not recommended to be modified + generateDirPath = "build/generated/flexi-locale" + // Customize the generated package name + // Android projects use the "namespace" in the "android" configuration method block by default + // You don't need to set it, the package name will be automatically matched under normal circumstances + packageName = "com.example.mydemo" + // Customize the generated class name + // By default, the name of the current project + "Locale" is used + // You don't need to set it, the class name will be automatically matched under normal circumstances + className = "MyDemo" + // Whether to enable restricted access function + // Not enabled by default. When enabled, the "internal" modifier will be added to the generated classes and methods + // If your project is a tool library or dependency, it is usually recommended to enable it + // Once enabled, it can prevent other developers from calling your project's i18ns string calling classes when they reference your library + isEnableRestrictedAccess = false + } +} +``` + +If you want to use it in Groovy DSL, please change the `=` of all variables to spaces, delete the `is` in front of `Enable` and lowercase `E`. + +## Usage Example + +`FlexiLocale` will automatically scan all XML files containing `...` in the `res/values` directory of the current project. + +Assume this is the `strings.xml` of your current project, divided into two directories: `default` and `zh-rCN`. + +> values/strings.xml +```xml + + My App + Hello %1$s + +``` + +> values-zh-rCN/strings.xml + +```xml + + 我的应用 + 你好 %1$s + +``` + +We recommend loading `AppLocale` in `Application` so you can call it from anywhere in your application. + +> The following example + +```kotlin +lateinit var locale: AppLocale + +class App : Application() { + + override fun onCreate() { + locale = AppLocale.attach(this) + } +} +``` + +Next, you can use these strings directly. + +> The following example + +```kotlin +class MainActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + // Will set the title to: + // default: My App + // zh-rCN: 我的应用 + actionBar.title = locale.appName + // Will set the text to: + // default: Hello John + // zh-rCN: 你好 John + findViewById(R.id.hello_text).text = locale.sayHello("John") + } +} +``` + +To use Java, you only need to add `get` before the name of the string method to be called. + +It should be noted that when you modify resource files such as `strings.xml`, you need to re-run Gradle Sync to get the latest generation results. + +### Dynamic Refresh + +If the user dynamically changes the system language, you can use the `attach { dynamicResources }` method in `AppLocale` to set a dynamic resources +instance. + +If you loaded `AppLocale` using `Context`, then you don't need to do anything. + +Please note that if the `Activity` is not restarted automatically, please manually call `recreate` in the `Activity` to see the results of the +language change. + +## Feedback + +If you encounter any problems while using `FlexiLocale`, you can always open an `issues` on GitHub to give us feedback. \ No newline at end of file diff --git a/flexilocal-gradle-plugin/.gitignore b/flexilocal-gradle-plugin/.gitignore new file mode 100644 index 0000000..66eaea9 --- /dev/null +++ b/flexilocal-gradle-plugin/.gitignore @@ -0,0 +1,2 @@ +.gradle +/build \ No newline at end of file diff --git a/flexilocal-gradle-plugin/build.gradle.kts b/flexilocal-gradle-plugin/build.gradle.kts new file mode 100644 index 0000000..674db15 --- /dev/null +++ b/flexilocal-gradle-plugin/build.gradle.kts @@ -0,0 +1,65 @@ +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) + 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() +} \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/FlexiLocale.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/FlexiLocale.kt new file mode 100644 index 0000000..0666a03 --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/FlexiLocale.kt @@ -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 +} \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/gradle/factory/ExtensionAwareFactory.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/gradle/factory/ExtensionAwareFactory.kt new file mode 100644 index 0000000..59513ba --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/gradle/factory/ExtensionAwareFactory.kt @@ -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).not()) 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 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).not()) 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 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 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 ExtensionAware.configure(name: String, configure: Action) = 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 \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/gradle/factory/GradleProjectFactory.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/gradle/factory/GradleProjectFactory.kt new file mode 100644 index 0000000..00dd2cb --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/gradle/factory/GradleProjectFactory.kt @@ -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() + + /** + * 递归子项目 + * @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) +} \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/gradle/proxy/IProjectLifecycle.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/gradle/proxy/IProjectLifecycle.kt new file mode 100644 index 0000000..8e15460 --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/gradle/proxy/IProjectLifecycle.kt @@ -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) +} \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/FlexiLocaleExtension.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/FlexiLocaleExtension.kt new file mode 100644 index 0000000..bf3fc2f --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/FlexiLocaleExtension.kt @@ -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.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) + } +} \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/FlexiLocalePlugin.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/FlexiLocalePlugin.kt new file mode 100644 index 0000000..18b3f3f --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/FlexiLocalePlugin.kt @@ -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 internal constructor() : Plugin { + + /** 当前扩展实例 */ + 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") + } +} \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/config/proxy/IFlexiLocaleConfigs.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/config/proxy/IFlexiLocaleConfigs.kt new file mode 100644 index 0000000..4afdc5b --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/config/proxy/IFlexiLocaleConfigs.kt @@ -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() +} \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/extension/dsl/configure/FlexiLocaleConfigureExtension.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/extension/dsl/configure/FlexiLocaleConfigureExtension.kt new file mode 100644 index 0000000..b58d77c --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/extension/dsl/configure/FlexiLocaleConfigureExtension.kt @@ -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()).not()) + FError.make("Invalid package name \"$this\"") + } + + /** 检查合法类名 */ + fun String.checkingValidClassName() { + if (isNotBlank() && matches("^[a-zA-Z][a-zA-Z0-9_]*$".toRegex()).not()) + 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 + } + } +} \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/generator/LocaleSourcesGenerator.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/generator/LocaleSourcesGenerator.kt new file mode 100644 index 0000000..b75b8db --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/generator/LocaleSourcesGenerator.kt @@ -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> { 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>.toAutoWrapKeyValues(): List> { + val maxAllowLength = 75 + val punctuations = charArrayOf('.', '。', ',', ',', '、', ';', ';', ':', ':', '!', '!', '?', '?') + val result = mutableListOf>() + val placeholders = mutableListOf>() + 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(" ", "·") +} \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/generator/factory/GeneratorFactory.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/generator/factory/GeneratorFactory.kt new file mode 100644 index 0000000..41ea22c --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/generator/factory/GeneratorFactory.kt @@ -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 + +/** I18ns (子键值对) 数组类型定义 */ +internal typealias LocaleChildMap = MutableMap + +/** I18ns (文件) 数组类型定义 */ +internal typealias LocaleFileMap = MutableMap> \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/helper/LocaleAnalysisHelper.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/helper/LocaleAnalysisHelper.kt new file mode 100644 index 0000000..cee8f9f --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/helper/LocaleAnalysisHelper.kt @@ -0,0 +1,205 @@ +/* + * 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.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" + + /** I18ns 代码生成实例 */ + private val generator = LocaleSourcesGenerator() + + /** 当前全部 I18ns 数据 (来自但不一定完全为 strings.xml) */ + private val mappedStrings: LocaleStringMap = mutableMapOf() + + /** 当前项目命名空间 */ + private var namespace = "" + + /** 当前项目资源目录数组 */ + private val resDirectories = mutableListOf() + + /** 上次修改的 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.not()) 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.not() && isConfigsModified.not()) 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 BaseVariant.updateResDirectories() = sourceSets.forEach { provide -> provide.resDirectories?.also { resDirectories.addAll(it) } } + project.plugins.withId(APPLICATION_PLUGIN_NAME) { + project.get().also { extension -> + namespace = extension.namespace ?: "" + extension.applicationVariants.forEach { variant -> + variant.updateResDirectories() + }; extension.updateSourceDirs() + } + } + project.plugins.withId(LIBRARY_PLUGIN_NAME) { + project.get().also { extension -> + namespace = extension.namespace ?: "" + extension.libraryVariants.forEach { variant -> + variant.updateResDirectories() + }; 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.allValuesDirs(): LocaleFileMap { + val valuesDirs: LocaleFileMap = mutableMapOf() + forEach { + it.listFiles()?.filter { dir -> dir.name.startsWith("values") }?.forEach eachDir@{ valuesDir -> + if (valuesDir.exists().not() || valuesDir.isDirectory.not()) 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 + } +} \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/debug/FError.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/debug/FError.kt new file mode 100644 index 0000000..80a0b55 --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/debug/FError.kt @@ -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") +} \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/debug/FLog.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/debug/FLog.kt new file mode 100644 index 0000000..4a4b89a --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/debug/FLog.kt @@ -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) + } +} \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/factory/FileFactory.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/factory/FileFactory.kt new file mode 100644 index 0000000..a6e72c5 --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/factory/FileFactory.kt @@ -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) \ No newline at end of file diff --git a/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/factory/VariableFactory.kt b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/factory/VariableFactory.kt new file mode 100644 index 0000000..df3e98f --- /dev/null +++ b/flexilocal-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/factory/VariableFactory.kt @@ -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() } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..37fe982 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,19 @@ +# Project Configuration +project.name=FlexiLocale +project.description=An easy generation Android i18ns string call Gradle plugin. +project.url=https://github.com/BetterAndroid/FlexiLocale +project.groupName=com.highcapable.flexilocale +project.moduleName=flexi-locale +project.version=1.0.0 +project.licence.name=Apache License 2.0 +project.licence.url=https://github.com/BetterAndroid/FlexiLocale/blob/master/LICENSE +project.developer.id="0" +project.developer.name=fankes +project.developer.email=qzmmcn@163.com +# Gradle Plugin Configuration +gradle.plugin.moduleName=${project.groupName}.gradle.plugin +gradle.plugin.implementationClass=${project.groupName}.plugin.FlexiLocalePlugin +# Maven Publish Configuration +maven.publish.scm.connection=scm:git:git://github.com/BetterAndroid/FlexiLocale.git +maven.publish.scm.developerConnection=scm:git:ssh://github.com/BetterAndroid/FlexiLocale.git +maven.publish.scm.url=https://github.com/BetterAndroid/FlexiLocale \ No newline at end of file diff --git a/gradle/sweet-dependency/sweet-dependency-config.yaml b/gradle/sweet-dependency/sweet-dependency-config.yaml new file mode 100644 index 0000000..7b38ab5 --- /dev/null +++ b/gradle/sweet-dependency/sweet-dependency-config.yaml @@ -0,0 +1,25 @@ +preferences: + autowire-on-sync-mode: UPDATE_OPTIONAL_DEPENDENCIES + repositories-mode: FAIL_ON_PROJECT_REPOS + +repositories: + gradle-plugin-portal: + scope: PLUGINS + google: + maven-central: + +plugins: + org.jetbrains.kotlin.jvm: + alias: kotlin-jvm + version: 1.9.10 + com.vanniktech.maven.publish: + alias: maven-publish + version: 0.25.3 + +libraries: + com.android.library: + com.android.library.gradle.plugin: + version: 8.1.2 + com.squareup: + kotlinpoet: + version: 1.14.2 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1b298ae --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/img-src/icon.png b/img-src/icon.png new file mode 100644 index 0000000..0c3d6c1 Binary files /dev/null and b/img-src/icon.png differ diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..46cb137 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +plugins { + id("com.highcapable.sweetdependency") version "1.0.2" + id("com.highcapable.sweetproperty") version "1.0.3" +} +sweetDependency { + isEnableVerboseMode = false +} +sweetProperty { + global { + sourcesCode { + className = rootProject.name + isEnableRestrictedAccess = true + } + } + rootProject { all { isEnable = false } } +} +rootProject.name = "FlexiLocale" +include(":flexilocal-gradle-plugin") \ No newline at end of file