Initial commit

This commit is contained in:
2023-10-13 17:42:25 +08:00
commit 78217514bf
41 changed files with 2571 additions and 0 deletions

17
.editorconfig Normal file
View File

@@ -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

42
.gitignore vendored Normal file
View File

@@ -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

10
.idea/.gitignore generated vendored Normal file
View File

@@ -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

BIN
.idea/icon.png generated Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -0,0 +1,13 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES" />
<inspection_tool class="ReplaceUntilWithRangeUntil" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
<inspection_tool class="UnstableApiUsage" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

6
.idea/kotlinc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.10" />
</component>
</project>

6
.idea/ktlint.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KtlintProjectConfiguration">
<treatAsErrors>false</treatAsErrors>
</component>
</project>

6
.idea/markdown.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<option name="showProblemsInCodeBlocks" value="false" />
</component>
</project>

202
LICENSE Normal file
View File

@@ -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.

100
README-zh-CN.md Normal file
View File

@@ -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)
<img src="https://github.com/BetterAndroid/FlexiLocale/blob/master/img-src/icon.png?raw=true" width = "100" height = "100" alt="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

105
README.md Normal file
View File

@@ -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)
<img src="https://github.com/BetterAndroid/FlexiLocale/blob/master/img-src/icon.png?raw=true" width = "100" height = "100" alt="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

16
build.gradle.kts Normal file
View File

@@ -0,0 +1,16 @@
plugins {
autowire(libs.plugins.kotlin.jvm) apply false
}
allprojects {
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = listOf(
"-Xno-param-assertions",
"-Xno-call-assertions",
"-Xno-receiver-assertions"
)
}
}
}

5
docs/changelog-zh-CN.md Normal file
View File

@@ -0,0 +1,5 @@
# 更新日志
## 1.0.0 | 2023.10.13
- 首个版本提交至 Maven

5
docs/changelog.md Normal file
View File

@@ -0,0 +1,5 @@
# Changelog
## 1.0.0 | 2023.10.13
- The first version is submitted to Maven

169
docs/guide-zh-CN.md Normal file
View File

@@ -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 "<version>"
}
```
请将上述代码中的 `<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` 目录中所有包含 `<string>...</string>` 的 XML 文件。
假设这是你当前项目的 `strings.xml`,分为 `default``zh-rCN` 两个目录。
> values/strings.xml
```xml
<resources>
<string name="app_name">My App</string>
<string name="say_hello">Hello %1$s</string>
</resources>
```
> values-zh-rCN/strings.xml
```xml
<resources>
<string name="app_name">我的应用</string>
<string name="say_hello">你好 %1$s</string>
</resources>
```
我们建议在 `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<TextView>(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` 向我们反馈。

176
docs/guide.md Normal file
View File

@@ -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 "<version>"
}
```
Please replace `<version>` 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 `<string>...</string>` 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
<resources>
<string name="app_name">My App</string>
<string name="say_hello">Hello %1$s</string>
</resources>
```
> values-zh-rCN/strings.xml
```xml
<resources>
<string name="app_name">我的应用</string>
<string name="say_hello">你好 %1$s</string>
</resources>
```
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<TextView>(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.

2
flexilocal-gradle-plugin/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.gradle
/build

View File

@@ -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()
}

View File

@@ -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
}

View File

@@ -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 <reified T> ExtensionAware.getOrCreate(name: String, vararg args: Any?) = name.toSafeExtName().let { sName ->
runCatching { extensions.create(sName, T::class.java, *args) as T }.getOrElse {
if ((it is IllegalArgumentException && it.message?.startsWith("Cannot add extension with name") == true).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 <reified T> ExtensionAware.get(name: String) =
runCatching { extensions.getByName(name) as T }.getOrNull() ?: FError.make("Could not get extension with name \"$name\"")
/**
* 获取扩展方法 - 目标对象 [T]
* @return [T]
*/
internal inline fun <reified T> ExtensionAware.get() =
runCatching { extensions.getByType(T::class.java) as T }.getOrNull() ?: FError.make("Could not get extension with type ${T::class.java}")
/**
* 配置扩展方法 - 目标对象 [T]
* @param name 方法名称
* @param configure 配置方法体
*/
internal inline fun <reified T> ExtensionAware.configure(name: String, configure: Action<T>) = extensions.configure(name, configure)
/**
* 是否存在扩展方法
* @param name 方法名称
* @return [Boolean]
*/
internal fun ExtensionAware.hasExtension(name: String) = runCatching { extensions.getByName(name); true }.getOrNull() ?: false
/**
* 转换到扩展方法类型 [ExtensionAware]
* @return [ExtensionAware]
* @throws IllegalStateException 如果类型不是 [ExtensionAware]
*/
internal fun Any.asExtension() = this as? ExtensionAware? ?: FError.make("This instance \"$this\" is not a valid Extension")
/**
* 由于 Gradle 存在一个 [ExtensionAware] 的扩展
*
* 此功能用于检测当前字符串是否为 Gradle 使用的关键字名称
* @return [Boolean]
*/
internal fun String.isUnSafeExtName() = camelcase().let { it == "ext" || it == "extra" || it == "extraProperties" || it == "extensions" }
/**
* 由于 Gradle 存在一个 [ExtensionAware] 的扩展
*
* 此功能用于转换不符合规定的字符串到 "{字符串}s"
* @return [String]
*/
internal fun String.toSafeExtName() = if (isUnSafeExtName()) "${this}s" else this

View File

@@ -0,0 +1,42 @@
/*
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiLocale
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/10/11.
*/
package com.highcapable.flexilocale.gradle.factory
import org.gradle.api.Project
/**
* 获取指定项目的完整名称 (无子项目前冒号)
* @return [String]
*/
internal fun Project.fullName(): String {
val baseNames = mutableListOf<String>()
/**
* 递归子项目
* @param project 当前项目
*/
fun fetchChild(project: Project) {
project.parent?.also { if (it != it.rootProject) fetchChild(it) }
baseNames.add(project.name)
}; fetchChild(project = this)
return buildString { baseNames.onEach { append(":$it") }.clear() }.drop(1)
}

View File

@@ -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)
}

View File

@@ -0,0 +1,57 @@
/*
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiLocale
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/10/10.
*/
package com.highcapable.flexilocale.plugin
import com.highcapable.flexilocale.FlexiLocale
import com.highcapable.flexilocale.gradle.factory.get
import com.highcapable.flexilocale.gradle.factory.getOrCreate
import com.highcapable.flexilocale.gradle.proxy.IProjectLifecycle
import com.highcapable.flexilocale.plugin.extension.dsl.configure.FlexiLocaleConfigureExtension
import com.highcapable.flexilocale.plugin.helper.LocaleAnalysisHelper
import com.highcapable.flexilocale.utils.debug.FError
import org.gradle.api.Project
/**
* [FlexiLocale] 插件扩展类
*/
internal class FlexiLocaleExtension internal constructor() : IProjectLifecycle {
private companion object {
/** Android Gradle plugin 扩展名称 */
private const val ANDROID_EXTENSION_NAME = "android"
}
/** 当前配置方法体实例 */
private var configure: FlexiLocaleConfigureExtension? = null
override fun onLoaded(project: Project) {
runCatching {
configure = project.get(ANDROID_EXTENSION_NAME).getOrCreate<FlexiLocaleConfigureExtension>(FlexiLocaleConfigureExtension.NAME)
}.onFailure { FError.make("Configure $project got an error, ${FlexiLocale.TAG} can only supports Android projects\nCaused by: $it") }
}
override fun onEvaluate(project: Project) {
val configs = configure?.build(project) ?: FError.make("Extension \"${FlexiLocaleConfigureExtension.NAME}\" create failed")
LocaleAnalysisHelper.start(project, configs)
}
}

View File

@@ -0,0 +1,47 @@
/*
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiLocale
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/10/10.
*/
@file:Suppress("unused")
package com.highcapable.flexilocale.plugin
import com.highcapable.flexilocale.FlexiLocale
import com.highcapable.flexilocale.utils.debug.FError
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.ExtensionAware
/**
* [FlexiLocale] 插件定义类
*/
class FlexiLocalePlugin<T : ExtensionAware> internal constructor() : Plugin<T> {
/** 当前扩展实例 */
private val extension = FlexiLocaleExtension()
override fun apply(target: T) = when (target) {
is Project -> {
extension.onLoaded(target)
target.afterEvaluate { extension.onEvaluate(project = this) }
}
else -> FError.make("${FlexiLocale.TAG} can only applied in build.gradle or build.gradle.kts, but current is $target")
}
}

View File

@@ -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()
}

View File

@@ -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
}
}
}

View File

@@ -0,0 +1,242 @@
/*
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiLocale
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/10/10.
*/
package com.highcapable.flexilocale.plugin.generator
import com.highcapable.flexilocale.FlexiLocale
import com.highcapable.flexilocale.plugin.config.proxy.IFlexiLocaleConfigs
import com.highcapable.flexilocale.plugin.generator.factory.LocaleStringMap
import com.highcapable.flexilocale.utils.debug.FError
import com.highcapable.flexilocale.utils.factory.camelcase
import com.highcapable.flexilocale.utils.factory.uppercamelcase
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.LambdaTypeName
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asTypeName
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.abs
/**
* I18ns 生成工具类
*/
internal class LocaleSourcesGenerator {
/**
* 生成 [FileSpec]
* @param configs 当前配置
* @param keyValues 键值数组
* @param namespace 命名空间
* @param packageName 包名
* @return [FileSpec]
* @throws IllegalStateException 如果生成失败
*/
internal fun build(
configs: IFlexiLocaleConfigs,
keyValues: LocaleStringMap,
namespace: String,
packageName: String
) = runCatching {
FileSpec.builder(packageName, configs.className).apply {
val selfClass = ClassName(packageName, configs.className)
val contextClass = ClassName("android.content", "Context")
val resourcesClass = ClassName("android.content.res", "Resources")
val resourcesInitializer = LambdaTypeName.get(returnType = resourcesClass, parameters = emptyList())
addAnnotation(AnnotationSpec.builder(Suppress::class).addMember("\"StringFormatInvalid\"").build())
addImport(namespace, "R")
addType(TypeSpec.classBuilder(selfClass).apply {
addKdoc(
"""
This class is generated by ${FlexiLocale.TAG} at ${SimpleDateFormat.getDateTimeInstance().format(Date())}
The content here is automatically generated according to the res/values of your projects
You can visit [here](${FlexiLocale.PROJECT_URL}) for more help
""".trimIndent()
)
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
addFunction(FunSpec.constructorBuilder().addModifiers(KModifier.PRIVATE).build())
addProperty(PropertySpec.builder("context", contextClass.copy(nullable = true)).apply {
addKdoc("The current [Context] for this app or library")
addModifiers(KModifier.PRIVATE)
mutable()
initializer("null")
}.build())
addProperty(PropertySpec.builder("resources", resourcesClass.copy(nullable = true)).apply {
addKdoc("The current [Resources] for this app or library")
addModifiers(KModifier.PRIVATE)
mutable()
initializer("null")
}.build())
addProperty(PropertySpec.builder("resourcesInitializer", resourcesInitializer.copy(nullable = true)).apply {
addKdoc("The current [Resources] initializer for this app or library")
addModifiers(KModifier.PRIVATE)
mutable()
initializer("null")
}.build())
addType(TypeSpec.companionObjectBuilder().apply {
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
addFunction(FunSpec.builder("attach").apply {
addKdoc(
"""
Attach [${selfClass.simpleName}] to [Context]
@param context like [android.app.Application] or [android.app.Activity]
@return [${selfClass.simpleName}]
""".trimIndent()
)
addAnnotation(JvmStatic::class)
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
addParameter("context", contextClass)
addStatement("return ${selfClass.simpleName}().apply { this.context = context }")
returns(selfClass)
}.build())
addFunction(FunSpec.builder("attach").apply {
addKdoc(
"""
Attach [${selfClass.simpleName}] to [Resources]
- Note: this method will have no effect if [context] already exists
@param resources A [Resources] that exists and has not been recycled
@return [${selfClass.simpleName}]
""".trimIndent()
)
addAnnotation(JvmStatic::class)
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
addParameter("resources", resourcesClass)
addStatement("return ${selfClass.simpleName}().apply { this.resources = resources }")
returns(selfClass)
}.build())
addFunction(FunSpec.builder("attach").apply {
addKdoc(
"""
Attach [${selfClass.simpleName}] to [Resources] initializer
- Note: this method will have no effect if [context] already exists
@param resourcesInitializer A [Resources] initializer returns a non-recycled instance
@return [${selfClass.simpleName}]
""".trimIndent()
)
addAnnotation(JvmStatic::class)
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
addParameter("resourcesInitializer", resourcesInitializer)
addStatement("return ${selfClass.simpleName}().apply { this.resourcesInitializer = resourcesInitializer }")
returns(selfClass)
}.build())
}.build())
addProperty(PropertySpec.builder("currentResources", resourcesClass).apply {
addKdoc("The current used [Resources] for this app or library")
addModifiers(KModifier.PRIVATE)
getter(FunSpec.getterBuilder().apply {
addStatement("return context?.resources ?: resourcesInitializer?.invoke() ?: resources" +
"?: error(\"${("Unable to get Resource instance, the app may have been killed " +
"or initialization process failed").toKotlinPoetSpace()}\")")
}.build())
}.build())
keyValues.forEach { (key, contentValues) ->
val fixedKey = key.camelcase()
val getterKey = "get${key.uppercamelcase()}"
val statement = "return currentResources.getString(R.string.$key, *formatArgs)"
var kDoc = "Resolve the [R.string.$key]\n\n"
if (contentValues.isNotEmpty()) kDoc += "| Configuration | Value |\n| --- | --- |\n"
contentValues.toList()
.sortedWith(compareBy<Pair<String, String>> { it.first != "default" }.thenBy { it.first })
.toAutoWrapKeyValues()
.forEach { (key, value) ->
val displayValue = value.replace("%".toRegex(), "%%")
kDoc += "| $key | $displayValue |\n"
}; kDoc = kDoc.trim()
addProperty(PropertySpec.builder(fixedKey, String::class).apply {
addKdoc(kDoc)
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
getter(FunSpec.getterBuilder().apply {
addAnnotation(AnnotationSpec.builder(JvmName::class).addMember("\"$getterKey\"").build())
addStatement("return $fixedKey()")
}.build())
}.build())
addFunction(FunSpec.builder(fixedKey).apply {
addKdoc("$kDoc\n@param formatArgs The format arguments that will be used for substitution")
addAnnotation(AnnotationSpec.builder(JvmName::class).addMember("\"$getterKey\"").build())
if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL)
addParameter(ParameterSpec.builder("formatArgs", Any::class.asTypeName()).addModifiers(KModifier.VARARG).build())
addStatement(statement)
returns(String::class)
}.build())
}
}.build())
}.build()
}.getOrElse { FError.make("Failed to generated Kotlin file\n$it") }
/**
* 转换为自动换行键值对数组
* @return [List]<[Pair]<[String], [String]>>
*/
private fun List<Pair<String, String>>.toAutoWrapKeyValues(): List<Pair<String, String>> {
val maxAllowLength = 75
val punctuations = charArrayOf('.', '。', ',', '', '、', ';', '', ':', '', '!', '', '?', '')
val result = mutableListOf<Pair<String, String>>()
val placeholders = mutableListOf<Pair<String, String>>()
forEach {
var key = it.first
var value = it.second.replace("\\n", "")
val maxLength = abs(maxAllowLength - key.length)
while (value.length > maxLength) {
var splitIndex = maxLength
var splitValue = value.substring(0, splitIndex)
val lastSpaceIndex = splitValue.lastIndexOf(' ')
val lastPunctuationIndex = splitValue.lastIndexOfAny(punctuations)
val hashWrapIndex = splitValue.lastIndexOf('')
when {
hashWrapIndex != -1 && (hashWrapIndex < lastSpaceIndex || hashWrapIndex < lastPunctuationIndex) -> {
splitIndex = hashWrapIndex
splitValue = value.substring(0, splitIndex)
}
lastSpaceIndex != -1 && lastSpaceIndex >= lastPunctuationIndex -> {
splitIndex = lastSpaceIndex + 1
splitValue = value.substring(0, splitIndex)
}
lastPunctuationIndex != -1 -> {
splitIndex = lastPunctuationIndex + 1
splitValue = value.substring(0, splitIndex)
}
}
value = value.substring(splitIndex).trimStart('')
result.add(key to splitValue)
key = " ".repeat(key.length)
}
if (value.isNotEmpty())
result.add(key to value.replace("", ""))
else placeholders.add(key to "")
}; result.addAll(placeholders)
return result
}
/**
* 转换到 KotlinPoet 声明的空格
* @return [String]
*/
private fun String.toKotlinPoetSpace() = replace(" ", "·")
}

View File

@@ -0,0 +1,33 @@
/*
* FlexiLocale - An easy generation Android i18ns string call Gradle plugin.
* Copyright (C) 2019-2023 HighCapable
* https://github.com/BetterAndroid/FlexiLocale
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2023/10/13.
*/
package com.highcapable.flexilocale.plugin.generator.factory
import java.io.File
/** I18ns 数组类型定义 */
internal typealias LocaleStringMap = MutableMap<String, LocaleChildMap>
/** I18ns (子键值对) 数组类型定义 */
internal typealias LocaleChildMap = MutableMap<String, String>
/** I18ns (文件) 数组类型定义 */
internal typealias LocaleFileMap = MutableMap<String, MutableSet<File>>

View File

@@ -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<File>()
/** 上次修改的 Hash Code */
private var lastModifiedHashCode = 0
/** 配置是否已被修改 */
private var isConfigsModified = true
/** 当前使用的配置实例 */
private lateinit var configs: IFlexiLocaleConfigs
/**
* 开始分析当前项目
* @param project 当前项目
* @param configs 当前配置
*/
internal fun start(project: Project, configs: IFlexiLocaleConfigs) {
this.configs = configs
if (configs.isEnable.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<AppExtension>().also { extension ->
namespace = extension.namespace ?: ""
extension.applicationVariants.forEach { variant ->
variant.updateResDirectories()
}; extension.updateSourceDirs()
}
}
project.plugins.withId(LIBRARY_PLUGIN_NAME) {
project.get<LibraryExtension>().also { extension ->
namespace = extension.namespace ?: ""
extension.libraryVariants.forEach { variant ->
variant.updateResDirectories()
}; extension.updateSourceDirs()
}
}
}.onFailure { FError.make("Failed to initialize Android Gradle plugin, this may be not or a wrong Android project\n$it") }
}
/** 更新生成后的代码内容 */
private fun updateGeneration() {
val packageName = "${configs.packageName.ifBlank { namespace }}.generated.locale"
val generateDir = configs.generateDirPath.toFile().apply { if (exists() && isDirectory) deleteRecursively() }
generator.build(configs, mappedStrings, namespace, packageName).writeTo(generateDir)
}
/**
* 解析当前资源目录下的全部可用 values 目录数组 (包含 I18ns 数据)
* @return [LocaleFileMap]
*/
private fun List<File>.allValuesDirs(): LocaleFileMap {
val valuesDirs: LocaleFileMap = mutableMapOf()
forEach {
it.listFiles()?.filter { dir -> dir.name.startsWith("values") }?.forEach eachDir@{ valuesDir ->
if (valuesDir.exists().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
}
}

View File

@@ -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")
}

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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() }

19
gradle.properties Normal file
View File

@@ -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

View File

@@ -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

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -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

234
gradlew vendored Executable file
View File

@@ -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" "$@"

89
gradlew.bat vendored Normal file
View File

@@ -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

BIN
img-src/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

25
settings.gradle.kts Normal file
View File

@@ -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")