├── .editorconfig ├── .gitignore ├── .idea ├── icon.png ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── ktlint-plugin.xml ├── ktlint.xml ├── markdown.xml └── vcs.xml ├── LICENSE ├── README-zh-CN.md ├── README.md ├── build.gradle.kts ├── docs ├── changelog-zh-CN.md ├── changelog.md ├── guide-zh-CN.md └── guide.md ├── flexilocale-gradle-plugin ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── com │ └── highcapable │ └── flexilocale │ ├── FlexiLocale.kt │ ├── gradle │ ├── factory │ │ ├── ExtensionAwareFactory.kt │ │ └── GradleProjectFactory.kt │ └── proxy │ │ └── IProjectLifecycle.kt │ ├── plugin │ ├── FlexiLocaleExtension.kt │ ├── FlexiLocalePlugin.kt │ ├── config │ │ └── proxy │ │ │ └── IFlexiLocaleConfigs.kt │ ├── extension │ │ └── dsl │ │ │ └── configure │ │ │ └── FlexiLocaleConfigureExtension.kt │ ├── generator │ │ ├── LocaleSourcesGenerator.kt │ │ └── factory │ │ │ └── GeneratorFactory.kt │ └── helper │ │ └── LocaleAnalysisHelper.kt │ └── utils │ ├── debug │ ├── FError.kt │ └── FLog.kt │ └── factory │ ├── FileFactory.kt │ └── VariableFactory.kt ├── gradle.properties ├── gradle ├── sweet-dependency │ └── sweet-dependency-config.yaml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img-src └── icon.png └── settings.gradle.kts /.editorconfig: -------------------------------------------------------------------------------- 1 | # noinspection EditorConfigKeyCorrectness 2 | [{*.kt,*.kts}] 3 | ktlint_standard_annotation = disabled 4 | ktlint_standard_filename = disabled 5 | ktlint_standard_wrapping = disabled 6 | ktlint_standard_import-ordering = enabled 7 | ktlint_standard_max-line-length = disabled 8 | ktlint_standard_multiline-if-else = disabled 9 | ktlint_standard_argument-list-wrapping = disabled 10 | ktlint_standard_parameter-list-wrapping = disabled 11 | ktlint_standard_trailing-comma-on-declaration-site = disabled 12 | ktlint_function_signature_body_expression_wrapping = multiline 13 | ktlint_standard_string-template-indent = disabled 14 | ktlint_standard_function-signature = disabled 15 | ktlint_standard_trailing-comma-on-call-site = disabled 16 | ktlint_standard_multiline-expression-wrapping = disabled 17 | ktlint_standard_no-empty-first-line-in-class-body = disabled 18 | ktlint_standard_if-else-wrapping = disabled 19 | ktlint_standard_if-else-bracing = disabled 20 | ktlint_standard_statement-wrapping = disabled 21 | ktlint_standard_blank-line-before-declaration = disabled 22 | ktlint_standard_no-empty-file = disabled 23 | ktlint_standard_property-naming = disabled 24 | ktlint_standard_function-naming = disabled 25 | ktlint_standard_chain-method-continuation = disabled 26 | ktlint_standard_class-signature = disabled 27 | ktlint_standard_condition-wrapping = disabled 28 | ktlint_standard_class-signature = disabled 29 | ktlint_standard_no-trailing-spaces = disabled 30 | ktlint_standard_multiline-loop = disabled 31 | ij_continuation_indent_size = 2 32 | indent_size = 4 33 | indent_style = space 34 | insert_final_newline = false 35 | max_line_length = 150 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Fully .gtignore for IntelliJ, Android Studio and Gradle based Java projects 2 | ## References: 3 | ## - https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 4 | ## - https://github.com/android/platform-samples/blob/main/.gitignore 5 | 6 | # User-specific stuff 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/**/usage.statistics.xml 10 | .idea/**/dictionaries 11 | .idea/**/shelf 12 | 13 | # AWS User-specific 14 | .idea/**/aws.xml 15 | 16 | # Generated files 17 | .idea/**/contentModel.xml 18 | 19 | # Sensitive or high-churn files 20 | .idea/**/dataSources/ 21 | .idea/**/dataSources.ids 22 | .idea/**/dataSources.local.xml 23 | .idea/**/sqlDataSources.xml 24 | .idea/**/dynamic.xml 25 | .idea/**/uiDesigner.xml 26 | .idea/**/dbnavigator.xml 27 | 28 | # Gradle 29 | .idea/**/gradle.xml 30 | .idea/**/libraries 31 | 32 | # Gradle and Maven with auto-import 33 | .idea/.name 34 | .idea/artifacts 35 | .idea/compiler.xml 36 | .idea/jarRepositories.xml 37 | .idea/modules.xml 38 | .idea/*.iml 39 | .idea/modules 40 | .idea/caches 41 | .idea/material_theme** 42 | .idea/other.xml 43 | *.iml 44 | *.ipr 45 | 46 | # Kotlin 47 | .kotlin 48 | 49 | # Misc 50 | .idea/misc.xml 51 | 52 | # CMake 53 | cmake-build-*/ 54 | 55 | # Mongo Explorer plugin 56 | .idea/**/mongoSettings.xml 57 | 58 | # File-based project format 59 | *.iws 60 | 61 | # IntelliJ 62 | out/ 63 | 64 | # mpeltonen/sbt-idea plugin 65 | .idea_modules/ 66 | 67 | # JIRA plugin 68 | atlassian-ide-plugin.xml 69 | 70 | # Cursive Clojure plugin 71 | .idea/replstate.xml 72 | 73 | # SonarLint plugin 74 | .idea/sonarlint/ 75 | 76 | # Crashlytics plugin (for Android Studio and IntelliJ) 77 | com_crashlytics_export_strings.xml 78 | crashlytics.properties 79 | crashlytics-build.properties 80 | fabric.properties 81 | 82 | # Editor-based Rest Client 83 | .idea/httpRequests 84 | 85 | # Android studio 3.1+ serialized cache file 86 | .idea/caches/build_file_checksums.ser 87 | 88 | # Android studio 3.1+ additional 89 | .idea/deployment*.xml 90 | .idea/assetWizardSettings.xml 91 | .idea/androidTestResultsUserPreferences.xml 92 | 93 | # Android projects 94 | **/local.properties 95 | /captures 96 | .externalNativeBuild 97 | .cxx 98 | 99 | # Gradle projects 100 | .gradle 101 | build/ 102 | 103 | # Mkdocs temporary serving folder 104 | docs-gen 105 | site 106 | *.bak 107 | .idea/appInsightsSettings.xml 108 | 109 | # Mac OS 110 | .DS_Store -------------------------------------------------------------------------------- /.idea/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BetterAndroid/FlexiLocale/56ff6a109aaecb851b09c82b6f4170a3c33fad10/.idea/icon.png -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/ktlint-plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MANUAL 5 | false 6 | 7 | -------------------------------------------------------------------------------- /.idea/ktlint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | -------------------------------------------------------------------------------- /.idea/markdown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright HighCapable [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | https://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README-zh-CN.md: -------------------------------------------------------------------------------- 1 | # Flexi Locale 2 | 3 | [![GitHub license](https://img.shields.io/github/license/BetterAndroid/FlexiLocale?color=blue)](https://github.com/BetterAndroid/FlexiLocale/blob/master/LICENSE) 4 | [![GitHub release](https://img.shields.io/github/v/release/BetterAndroid/FlexiLocale?display_name=release&logo=github&color=green)](https://github.com/BetterAndroid/FlexiLocale/releases) 5 | [![Telegram](https://img.shields.io/badge/discussion-Telegram-blue.svg?logo=telegram)](https://t.me/BetterAndroid) 6 | [![Telegram](https://img.shields.io/badge/discussion%20dev-Telegram-blue.svg?logo=telegram)](https://t.me/HighCapable_Dev) 7 | [![QQ](https://img.shields.io/badge/discussion%20dev-QQ-blue.svg?logo=tencent-qq&logoColor=red)](https://qm.qq.com/cgi-bin/qm/qr?k=Pnsc5RY6N2mBKFjOLPiYldbAbprAU3V7&jump_from=webapi&authKey=X5EsOVzLXt1dRunge8ryTxDRrh9/IiW1Pua75eDLh9RE3KXE+bwXIYF5cWri/9lf) 8 | 9 | LOGO 10 | 11 | 一个自动为 Android 项目生成国际化字符串调用的 Gradle 插件。 12 | 13 | [English](README.md) | 简体中文 14 | 15 | | LOGO | [BetterAndroid](https://github.com/BetterAndroid) | 16 | |---------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------| 17 | 18 | 这个项目属于上述组织,**点击上方链接关注这个组织**,发现更多好项目。 19 | 20 | ## 这是什么 21 | 22 | 这是一个用来自动为 Android 项目生成国际化字符串调用代码功能的 Gradle 插件。 23 | 24 | 在 Android 项目中,要使用国际化字符串,需要在 `strings.xml` 中进行定义,然后使用 `context.getString(R.string.xxx)` 的方式去调用,非常的繁琐和不灵活。 25 | 26 | 这就是这个项目诞生的原因,通过这个插件,你现在只需要实例化一次插件生成的 `AppLocale` 类,然后就可以在任意地方使用了。 27 | 28 | > 传统写法 29 | 30 | ```kotlin 31 | val appName = context.getString(R.string.app_name) 32 | ``` 33 | 34 | > 现代写法 35 | 36 | ```kotlin 37 | val locale by lazy { AppLocale.attach(context) } 38 | val appName = locale.appName 39 | ``` 40 | 41 | 如果你依然在使用 Java,那么写法保持不变。 42 | 43 | ```java 44 | var locale = AppLocale.attach(context); 45 | var appName = locale.getAppName(); 46 | ``` 47 | 48 | ## 兼容性 49 | 50 | 理论支持不是很旧的 Gradle,建议版本为 `7.x.x` 及以上。 51 | 52 | 支持包含 Kotlin 插件的 Android 项目,其它类型的项目暂不支持。 53 | 54 | > 构建脚本语言 55 | 56 | - Kotlin DSL 57 | 58 | 推荐优先使用此语言作为构建脚本语言,这也是目前 Gradle 推荐的语言。 59 | 60 | - Groovy DSL 61 | 62 | 部分功能可能无法兼容,在后期会逐渐放弃支持,且部分功能会无法使用。 63 | 64 | ## 开始使用 65 | 66 | - [点击这里](docs/guide-zh-CN.md) 查看使用文档 67 | 68 | ## 更新日志 69 | 70 | - [点击这里](docs/changelog-zh-CN.md) 查看历史更新日志 71 | 72 | ## 项目推广 73 | 74 | 75 |
76 |

嘿,还请君留步!👋

77 |

这里有 Android 开发工具、UI 设计、Gradle 插件、Xposed 模块和实用软件等相关项目。

78 |

如果下方的项目能为你提供帮助,不妨为我点个 star 吧!

79 |

所有项目免费、开源,遵循对应开源许可协议。

80 |

→ 查看更多关于我的项目,请点击这里 ←

81 |
82 | 83 | ## Star History 84 | 85 | ![Star History Chart](https://api.star-history.com/svg?repos=BetterAndroid/FlexiLocale&type=Date) 86 | 87 | ## 许可证 88 | 89 | - [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) 90 | 91 | ``` 92 | Apache License Version 2.0 93 | 94 | Copyright (C) 2019 HighCapable 95 | 96 | Licensed under the Apache License, Version 2.0 (the "License"); 97 | you may not use this file except in compliance with the License. 98 | You may obtain a copy of the License at 99 | 100 | https://www.apache.org/licenses/LICENSE-2.0 101 | 102 | Unless required by applicable law or agreed to in writing, software 103 | distributed under the License is distributed on an "AS IS" BASIS, 104 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 105 | See the License for the specific language governing permissions and 106 | limitations under the License. 107 | ``` 108 | 109 | 版权所有 © 2019 HighCapable -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flexi Locale 2 | 3 | [![GitHub license](https://img.shields.io/github/license/BetterAndroid/FlexiLocale?color=blue)](https://github.com/BetterAndroid/FlexiLocale/blob/master/LICENSE) 4 | [![GitHub release](https://img.shields.io/github/v/release/BetterAndroid/FlexiLocale?display_name=release&logo=github&color=green)](https://github.com/BetterAndroid/FlexiLocale/releases) 5 | [![Telegram](https://img.shields.io/badge/discussion-Telegram-blue.svg?logo=telegram)](https://t.me/BetterAndroid) 6 | [![Telegram](https://img.shields.io/badge/discussion%20dev-Telegram-blue.svg?logo=telegram)](https://t.me/HighCapable_Dev) 7 | [![QQ](https://img.shields.io/badge/discussion%20dev-QQ-blue.svg?logo=tencent-qq&logoColor=red)](https://qm.qq.com/cgi-bin/qm/qr?k=Pnsc5RY6N2mBKFjOLPiYldbAbprAU3V7&jump_from=webapi&authKey=X5EsOVzLXt1dRunge8ryTxDRrh9/IiW1Pua75eDLh9RE3KXE+bwXIYF5cWri/9lf) 8 | 9 | LOGO 10 | 11 | An easy generation Android i18ns string call Gradle plugin. 12 | 13 | English | [简体中文](README-zh-CN.md) 14 | 15 | | LOGO | [BetterAndroid](https://github.com/BetterAndroid) | 16 | |---------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------| 17 | 18 | This project belongs to the above-mentioned organization, **click the link above to follow this organization** and discover more good projects. 19 | 20 | ## What's this 21 | 22 | This is a Gradle plugin for automatically generating i18ns string calling code functions for Android projects. 23 | 24 | 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 25 | is very cumbersome and inflexible. 26 | 27 | That's why this project was born. 28 | 29 | With this plugin, you now only need to instantiate the `AppLocale` class generated by the plugin once, and then you can use it anywhere. 30 | 31 | > Traditional Style 32 | 33 | ```kotlin 34 | val appName = context.getString(R.string.app_name) 35 | ``` 36 | 37 | > Modern Style 38 | 39 | ```kotlin 40 | val locale by lazy { AppLocale.attach(context) } 41 | val appName = locale.appName 42 | ``` 43 | 44 | If you are still using Java, the writing method remains the same. 45 | 46 | ```java 47 | var locale = AppLocale.attach(context); 48 | var appName = locale.getAppName(); 49 | ``` 50 | 51 | ## Compatibility 52 | 53 | The theory supports not very old Gradle, the recommended version is `7.x.x` and above. 54 | 55 | Android projects containing Kotlin plugins are supported, other types of projects are not supported yet. 56 | 57 | > Build Script Language 58 | 59 | - Kotlin DSL 60 | 61 | It is recommended to use this language as the build script language first, which is also the language currently recommended by Gradle. 62 | 63 | - Groovy DSL 64 | 65 | Some functions may be incompatible, support will be gradually dropped in the future, and some functions may become unavailable. 66 | 67 | ## Get Started 68 | 69 | - [Click here](docs/guide.md) to view the documentation 70 | 71 | ## Changelog 72 | 73 | - [Click here](docs/changelog.md) to view the historical changelog 74 | 75 | ## Promotion 76 | 77 | 78 |
79 |

Hey, please stay! 👋

80 |

Here are related projects such as Android development tools, UI design, Gradle plugins, Xposed Modules and practical software.

81 |

If the project below can help you, please give me a star!

82 |

All projects are free, open source, and follow the corresponding open source license agreement.

83 |

→ To see more about my projects, please click here ←

84 |
85 | 86 | ## Star History 87 | 88 | ![Star History Chart](https://api.star-history.com/svg?repos=BetterAndroid/FlexiLocale&type=Date) 89 | 90 | ## License 91 | 92 | - [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) 93 | 94 | ``` 95 | Apache License Version 2.0 96 | 97 | Copyright (C) 2019 HighCapable 98 | 99 | Licensed under the Apache License, Version 2.0 (the "License"); 100 | you may not use this file except in compliance with the License. 101 | You may obtain a copy of the License at 102 | 103 | https://www.apache.org/licenses/LICENSE-2.0 104 | 105 | Unless required by applicable law or agreed to in writing, software 106 | distributed under the License is distributed on an "AS IS" BASIS, 107 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 108 | See the License for the specific language governing permissions and 109 | limitations under the License. 110 | ``` 111 | 112 | Copyright © 2019 HighCapable -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | autowire(libs.plugins.kotlin.jvm) apply false 3 | autowire(libs.plugins.maven.publish) apply false 4 | } -------------------------------------------------------------------------------- /docs/changelog-zh-CN.md: -------------------------------------------------------------------------------- 1 | # 更新日志 2 | 3 | ## 1.0.0 | 2023.10.13 4 | 5 | - 首个版本提交至 Maven 6 | 7 | ## 1.0.1 | 2023.10.13 8 | 9 | - 修复在使用 Kotlin on Android 插件的项目上找不到源码路径的问题 -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.0 | 2023.10.13 4 | 5 | - The first version is submitted to Maven 6 | 7 | ## 1.0.1 | 2023.10.13 8 | 9 | - Fixed a problem where the source code path could not be found on projects using the Kotlin on Android plugin -------------------------------------------------------------------------------- /docs/guide-zh-CN.md: -------------------------------------------------------------------------------- 1 | # Flexi Locale 使用文档 2 | 3 | 在开始使用之前,建议你仔细阅读此文档,以便你能更好地了解它的作用方式与功能。 4 | 5 | 如果你的项目依然在使用 Groovy DSL 进行管理,推荐迁移到 Kotlin DSL。 6 | 7 | 在 Groovy DSL 中使用此插件发生的任何问题,我们都将不再负责排查和修复,并且在后期版本可能会完全不再支持 Groovy DSL。 8 | 9 | 注意:此文档中将不再详细介绍在 Groovy DSL 中的使用方法。 10 | 11 | ## 快速开始 12 | 13 | 我们推荐使用 [SweetDependency](https://github.com/HighCapable/SweetDependency) 来自动管理依赖版本。 14 | 15 | 以下是使用 `SweetDependency` 的装载方式。 16 | 17 | > 配置文件 18 | 19 | ```yaml 20 | plugins: 21 | com.highcapable.flexilocale: 22 | alias: flexi-locale 23 | version: + 24 | ``` 25 | 26 | > build.gradle.kts 27 | 28 | ```kotlin 29 | plugins { 30 | // 装载方式 1 31 | autowire(libs.plugins.flexi.locale) 32 | // 装载方式 2 33 | autowire("flexi-locale") 34 | } 35 | ``` 36 | 37 | 以下是传统的装载方式。 38 | 39 | 打开你需要集成 `FlexiLocale` 插件项目的 `build.gradle.kts`。 40 | 41 | > 示例如下 42 | 43 | ```kotlin 44 | plugins { 45 | id("com.highcapable.flexilocale") version "" 46 | } 47 | ``` 48 | 49 | 请将上述代码中的 `` 替换为 [Release](https://github.com/BetterAndroid/FlexiLocale/releases) 中的最新版本, 请注意不要在后方加入 apply false。 50 | 51 | 上述配置完成后,运行一次 Gradle Sync。 52 | 53 | 不出意外的情况下,你将会得到自动生成的 `AppLocale` 类,`Locale` 前方的名称为你的项目名称,默认应该为 `App`。 54 | 55 | ## 功能配置 56 | 57 | 你可以对 `FlexiLocale` 进行配置来实现自定义和个性化功能。 58 | 59 | `FlexiLocale` 为你提供了相对丰富的可自定义功能,下面是这些功能的说明与配置方法。 60 | 61 | 请在你的 `build.gradle.kts` 中添加 `flexiLocale` 方法块以开始配置 `FlexiLocale`。 62 | 63 | `FlexiLocale` 依附于 `android` 方法块生成。 64 | 65 | > 示例如下 66 | 67 | ```kotlin 68 | android { 69 | flexiLocale { 70 | // 启用 FlexiLocale,设置为 false 将禁用所有功能 71 | isEnable = true 72 | // 自定义生成的目录路径 73 | // 你可以填写相对于当前项目的路径 74 | // 默认为 "build/generated/flexi-locale" 75 | // 建议将生成的代码放置于 "build" 目录下,因为生成的代码不建议去修改它 76 | generateDirPath = "build/generated/flexi-locale" 77 | // 自定义生成的包名 78 | // Android 项目默认使用 "android" 配置方法块中的 "namespace" 79 | // 你可以不进行设置,包名在一般情况下会自动进行匹配 80 | packageName = "com.example.mydemo" 81 | // 自定义生成的类名 82 | // 默认使用当前项目的名称 + "Locale" 83 | // 你可以不进行设置,类名在一般情况下会自动进行匹配 84 | className = "MyDemo" 85 | // 是否启用受限访问功能 86 | // 默认不启用,启用后将为生成的类和方法添加 "internal" 修饰符 87 | // 如果你的项目为工具库或依赖,通常情况下建议启用 88 | // 启用后可以防止其他开发者在引用你的库时调用到你的项目国际化字符串调用类发生问题 89 | isEnableRestrictedAccess = false 90 | } 91 | } 92 | ``` 93 | 94 | 如需在 Groovy DSL 中使用,请将所有变量的 `=` 改为空格,并删除 `Enable` 前方的 `is` 并将 `E` 小写即可。 95 | 96 | ## 使用示例 97 | 98 | `FlexiLocale` 会自动扫描当前项目 `res/values` 目录中所有包含 `...` 的 XML 文件。 99 | 100 | 假设这是你当前项目的 `strings.xml`,分为 `default` 和 `zh-rCN` 两个目录。 101 | 102 | > values/strings.xml 103 | 104 | ```xml 105 | 106 | My App 107 | Hello %1$s 108 | 109 | ``` 110 | 111 | > values-zh-rCN/strings.xml 112 | 113 | ```xml 114 | 115 | 我的应用 116 | 你好 %1$s 117 | 118 | ``` 119 | 120 | 我们建议在 `Application` 中装载 `AppLocale`,这样你就可以在应用的任何地方进行调用。 121 | 122 | > 示例如下 123 | 124 | ```kotlin 125 | lateinit var locale: AppLocale 126 | 127 | class App : Application() { 128 | 129 | override fun onCreate() { 130 | locale = AppLocale.attach(this) 131 | } 132 | } 133 | ``` 134 | 135 | 下面,你就可以直接去使用这些字符串了。 136 | 137 | > 示例如下 138 | 139 | ```kotlin 140 | class MainActivity : Activity() { 141 | 142 | override fun onCreate(savedInstanceState: Bundle?) { 143 | // 将设置标题为: 144 | // default: My App 145 | // zh-rCN: 我的应用 146 | actionBar.title = locale.appName 147 | // 将设置文本为: 148 | // default: Hello John 149 | // zh-rCN: 你好 John 150 | findViewById(R.id.hello_text).text = locale.sayHello("John") 151 | } 152 | } 153 | ``` 154 | 155 | Java 的使用方式只需要将调用的字符串方法名前加入 `get` 即可。 156 | 157 | 需要注意的是,当你修改了 `strings.xml` 等资源文件,你需要重新运行一次 Gradle Sync 以得到最新的生成结果。 158 | 159 | ### 动态刷新 160 | 161 | 如果用户动态地修改了系统语言,你可以使用 `AppLocale` 中的 `attach { dynamicResources }` 方法设置动态资源实例。 162 | 163 | 如果你是使用 `Context` 装载的 `AppLocale`,那么你无需进行任何操作。 164 | 165 | 请注意,如果 `Activity` 未自动重新启动,请在 `Activity` 中手动调用 `recreate` 才能看到语言改变后的结果。 166 | 167 | ## 问题反馈 168 | 169 | 如果你在使用 `FlexiLocale` 的过程中遇到了任何问题,你都可以随时在 GitHub 开启一个 `issues` 向我们反馈。 -------------------------------------------------------------------------------- /docs/guide.md: -------------------------------------------------------------------------------- 1 | # Flexi Locale Documentation 2 | 3 | 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. 4 | 5 | If your project is still managed using Groovy DSL, it is recommended to migrate to Kotlin DSL. 6 | 7 | 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 8 | dropped entirely in later releases. 9 | 10 | Note: Usage in the Groovy DSL will not be detailed in this document. 11 | 12 | ## Quick Start 13 | 14 | We recommend using [SweetDependency](https://github.com/HighCapable/SweetDependency) to autowire dependencies versions. 15 | 16 | The following is the loading method using `SweetDependency`. 17 | 18 | > Configuration File 19 | 20 | ```yaml 21 | plugins: 22 | com.highcapable.flexilocale: 23 | alias: flexi-locale 24 | version: + 25 | ``` 26 | 27 | > build.gradle.kts 28 | 29 | ```kotlin 30 | plugins { 31 | // Loading method 1 32 | autowire(libs.plugins.flexi.locale) 33 | // Loading method 2 34 | autowire("flexi-locale") 35 | } 36 | ``` 37 | 38 | The following is the traditional loading method. 39 | 40 | Open the `build.gradle.kts` project where you need to integrate the `FlexiLocale` plugin. 41 | 42 | > The following example 43 | 44 | ```kotlin 45 | plugins { 46 | id("com.highcapable.flexilocale") version "" 47 | } 48 | ``` 49 | 50 | Please replace `` in the above code with the latest version in [Release](https://github.com/BetterAndroid/FlexiLocale/releases). 51 | 52 | Please be careful not to add apply false at the end. 53 | 54 | After the above configuration is completed, run Gradle Sync once. 55 | 56 | If nothing else goes wrong, you will get the automatically generated `AppLocale` class, 57 | the name in front of `Locale` is your project name, and the default should be `App`. 58 | 59 | ## Function Configuration 60 | 61 | You can configure `FlexiLocale` to achieve customization and personalization. 62 | 63 | `FlexiLocale` provides you with a relatively rich set of customizable functions. 64 | 65 | The following is the description and configuration method of these functions. 66 | 67 | Please add the `flexiLocale` method block to your `build.gradle.kts` to start configuring `FlexiLocale`. 68 | 69 | `FlexiLocale` depends on the `android` method block generation. 70 | 71 | > The following example 72 | 73 | ```kotlin 74 | android { 75 | flexiLocale { 76 | // Enable FlexiLocale, setting to false will disable all functionality 77 | isEnable = true 78 | // Customize the generated directory path 79 | // You can fill in the path relative to the current project 80 | // Default is "build/generated/flexi-locale" 81 | // It is recommended to place the generated code in the "build" directory, because the generated code is not recommended to be modified 82 | generateDirPath = "build/generated/flexi-locale" 83 | // Customize the generated package name 84 | // Android projects use the "namespace" in the "android" configuration method block by default 85 | // You don't need to set it, the package name will be automatically matched under normal circumstances 86 | packageName = "com.example.mydemo" 87 | // Customize the generated class name 88 | // By default, the name of the current project + "Locale" is used 89 | // You don't need to set it, the class name will be automatically matched under normal circumstances 90 | className = "MyDemo" 91 | // Whether to enable restricted access function 92 | // Not enabled by default. When enabled, the "internal" modifier will be added to the generated classes and methods 93 | // If your project is a tool library or dependency, it is usually recommended to enable it 94 | // Once enabled, it can prevent other developers from calling your project's i18ns string calling classes when they reference your library 95 | isEnableRestrictedAccess = false 96 | } 97 | } 98 | ``` 99 | 100 | 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`. 101 | 102 | ## Usage Example 103 | 104 | `FlexiLocale` will automatically scan all XML files containing `...` in the `res/values` directory of the current project. 105 | 106 | Assume this is the `strings.xml` of your current project, divided into two directories: `default` and `zh-rCN`. 107 | 108 | > values/strings.xml 109 | ```xml 110 | 111 | My App 112 | Hello %1$s 113 | 114 | ``` 115 | 116 | > values-zh-rCN/strings.xml 117 | 118 | ```xml 119 | 120 | 我的应用 121 | 你好 %1$s 122 | 123 | ``` 124 | 125 | We recommend loading `AppLocale` in `Application` so you can call it from anywhere in your application. 126 | 127 | > The following example 128 | 129 | ```kotlin 130 | lateinit var locale: AppLocale 131 | 132 | class App : Application() { 133 | 134 | override fun onCreate() { 135 | locale = AppLocale.attach(this) 136 | } 137 | } 138 | ``` 139 | 140 | Next, you can use these strings directly. 141 | 142 | > The following example 143 | 144 | ```kotlin 145 | class MainActivity : Activity() { 146 | 147 | override fun onCreate(savedInstanceState: Bundle?) { 148 | // Will set the title to: 149 | // default: My App 150 | // zh-rCN: 我的应用 151 | actionBar.title = locale.appName 152 | // Will set the text to: 153 | // default: Hello John 154 | // zh-rCN: 你好 John 155 | findViewById(R.id.hello_text).text = locale.sayHello("John") 156 | } 157 | } 158 | ``` 159 | 160 | To use Java, you only need to add `get` before the name of the string method to be called. 161 | 162 | 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. 163 | 164 | ### Dynamic Refresh 165 | 166 | If the user dynamically changes the system language, you can use the `attach { dynamicResources }` method in `AppLocale` to set a dynamic resources 167 | instance. 168 | 169 | If you loaded `AppLocale` using `Context`, then you don't need to do anything. 170 | 171 | Please note that if the `Activity` is not restarted automatically, please manually call `recreate` in the `Activity` to see the results of the 172 | language change. 173 | 174 | ## Feedback 175 | 176 | If you encounter any problems while using `FlexiLocale`, you can always open an `issues` on GitHub to give us feedback. -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | autowire(libs.plugins.kotlin.jvm) 4 | autowire(libs.plugins.maven.publish) 5 | } 6 | 7 | group = property.project.groupName 8 | version = property.project.version 9 | 10 | java { 11 | sourceCompatibility = JavaVersion.VERSION_21 12 | targetCompatibility = JavaVersion.VERSION_21 13 | withSourcesJar() 14 | } 15 | 16 | kotlin { 17 | jvmToolchain(21) 18 | sourceSets.all { languageSettings { languageVersion = "2.0" } } 19 | compilerOptions { 20 | freeCompilerArgs = listOf( 21 | "-Xno-param-assertions", 22 | "-Xno-call-assertions", 23 | "-Xno-receiver-assertions" 24 | ) 25 | } 26 | } 27 | 28 | dependencies { 29 | compileOnly(com.android.library.com.android.library.gradle.plugin) 30 | compileOnly(org.jetbrains.kotlin.kotlin.gradle.plugin) 31 | implementation(com.squareup.kotlinpoet) 32 | } 33 | 34 | gradlePlugin { 35 | plugins { 36 | create(property.project.moduleName) { 37 | id = property.project.groupName 38 | implementationClass = property.gradle.plugin.implementationClass 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/FlexiLocale.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/10. 21 | */ 22 | @file:Suppress("unused") 23 | 24 | package com.highcapable.flexilocale 25 | 26 | import com.highcapable.flexilocale.generated.FlexiLocaleProperties 27 | 28 | /** 29 | * [FlexiLocale] 的装载调用类 30 | */ 31 | object FlexiLocale { 32 | 33 | /** 标签名称 */ 34 | const val TAG = FlexiLocaleProperties.PROJECT_NAME 35 | 36 | /** 版本 */ 37 | const val VERSION = FlexiLocaleProperties.PROJECT_VERSION 38 | 39 | /** 项目地址 */ 40 | const val PROJECT_URL = FlexiLocaleProperties.PROJECT_URL 41 | } -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/gradle/factory/ExtensionAwareFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/11. 21 | */ 22 | @file:Suppress("unused", "USELESS_CAST", "KotlinRedundantDiagnosticSuppress") 23 | 24 | package com.highcapable.flexilocale.gradle.factory 25 | 26 | import com.highcapable.flexilocale.utils.debug.FError 27 | import com.highcapable.flexilocale.utils.factory.camelcase 28 | import org.gradle.api.Action 29 | import org.gradle.api.plugins.ExtensionAware 30 | 31 | /** 32 | * 创建、获取扩展方法 33 | * @param name 方法名称 - 自动调用 [toSafeExtName] 34 | * @param clazz 目标对象 [Class] 35 | * @param args 方法参数 36 | * @return [ExtensionAware] 37 | */ 38 | internal fun ExtensionAware.getOrCreate(name: String, clazz: Class<*>, vararg args: Any?) = name.toSafeExtName().let { sName -> 39 | runCatching { extensions.create(sName, clazz, *args).asExtension() }.getOrElse { 40 | if (!(it is IllegalArgumentException && it.message?.startsWith("Cannot add extension with name") == true)) throw it 41 | runCatching { extensions.getByName(sName).asExtension() }.getOrNull() ?: FError.make("Create or get extension failed with name \"$sName\"") 42 | } 43 | } 44 | 45 | /** 46 | * 创建、获取扩展方法 - 目标对象 [T] 47 | * @param name 方法名称 - 自动调用 [toSafeExtName] 48 | * @param args 方法参数 49 | * @return [T] 50 | */ 51 | internal inline fun ExtensionAware.getOrCreate(name: String, vararg args: Any?) = name.toSafeExtName().let { sName -> 52 | runCatching { extensions.create(sName, T::class.java, *args) as T }.getOrElse { 53 | if (!(it is IllegalArgumentException && it.message?.startsWith("Cannot add extension with name") == true)) throw it 54 | runCatching { extensions.getByName(sName) as? T? }.getOrNull() ?: FError.make("Create or get extension failed with name \"$sName\"") 55 | } 56 | } 57 | 58 | /** 59 | * 获取扩展方法 60 | * @param name 方法名称 61 | * @return [ExtensionAware] 62 | */ 63 | internal fun ExtensionAware.get(name: String) = 64 | runCatching { extensions.getByName(name).asExtension() }.getOrNull() ?: FError.make("Could not get extension with name \"$name\"") 65 | 66 | /** 67 | * 获取扩展方法 - 目标对象 [T] 68 | * @param name 方法名称 69 | * @return [T] 70 | */ 71 | internal inline fun ExtensionAware.get(name: String) = 72 | runCatching { extensions.getByName(name) as T }.getOrNull() ?: FError.make("Could not get extension with name \"$name\"") 73 | 74 | /** 75 | * 获取扩展方法 - 目标对象 [T] 76 | * @return [T] 77 | */ 78 | internal inline fun ExtensionAware.get() = 79 | runCatching { extensions.getByType(T::class.java) as T }.getOrNull() ?: FError.make("Could not get extension with type ${T::class.java}") 80 | 81 | /** 82 | * 配置扩展方法 - 目标对象 [T] 83 | * @param name 方法名称 84 | * @param configure 配置方法体 85 | */ 86 | internal inline fun ExtensionAware.configure(name: String, configure: Action) = extensions.configure(name, configure) 87 | 88 | /** 89 | * 是否存在扩展方法 90 | * @param name 方法名称 91 | * @return [Boolean] 92 | */ 93 | internal fun ExtensionAware.hasExtension(name: String) = runCatching { extensions.getByName(name); true }.getOrNull() ?: false 94 | 95 | /** 96 | * 转换到扩展方法类型 [ExtensionAware] 97 | * @return [ExtensionAware] 98 | * @throws IllegalStateException 如果类型不是 [ExtensionAware] 99 | */ 100 | internal fun Any.asExtension() = this as? ExtensionAware? ?: FError.make("This instance \"$this\" is not a valid Extension") 101 | 102 | /** 103 | * 由于 Gradle 存在一个 [ExtensionAware] 的扩展 104 | * 105 | * 此功能用于检测当前字符串是否为 Gradle 使用的关键字名称 106 | * @return [Boolean] 107 | */ 108 | internal fun String.isUnSafeExtName() = camelcase().let { it == "ext" || it == "extra" || it == "extraProperties" || it == "extensions" } 109 | 110 | /** 111 | * 由于 Gradle 存在一个 [ExtensionAware] 的扩展 112 | * 113 | * 此功能用于转换不符合规定的字符串到 "{字符串}s" 114 | * @return [String] 115 | */ 116 | internal fun String.toSafeExtName() = if (isUnSafeExtName()) "${this}s" else this -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/gradle/factory/GradleProjectFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/11. 21 | */ 22 | package com.highcapable.flexilocale.gradle.factory 23 | 24 | import org.gradle.api.Project 25 | 26 | /** 27 | * 获取指定项目的完整名称 (无子项目前冒号) 28 | * @return [String] 29 | */ 30 | internal fun Project.fullName(): String { 31 | val baseNames = mutableListOf() 32 | 33 | /** 34 | * 递归子项目 35 | * @param project 当前项目 36 | */ 37 | fun fetchChild(project: Project) { 38 | project.parent?.also { if (it != it.rootProject) fetchChild(it) } 39 | baseNames.add(project.name) 40 | }; fetchChild(project = this) 41 | return buildString { baseNames.onEach { append(":$it") }.clear() }.drop(1) 42 | } -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/gradle/proxy/IProjectLifecycle.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/10. 21 | */ 22 | package com.highcapable.flexilocale.gradle.proxy 23 | 24 | import org.gradle.api.Project 25 | 26 | /** 27 | * Gradle [Project] 生命周期接口 28 | */ 29 | internal interface IProjectLifecycle { 30 | 31 | /** 32 | * 当 Gradle 开始装载项目时回调 33 | * @param project 当前项目 34 | */ 35 | fun onLoaded(project: Project) 36 | 37 | /** 38 | * 当 Gradle 项目装载完成时回调 39 | * @param project 当前项目 40 | */ 41 | fun onEvaluate(project: Project) 42 | } -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/FlexiLocaleExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/10. 21 | */ 22 | package com.highcapable.flexilocale.plugin 23 | 24 | import com.highcapable.flexilocale.FlexiLocale 25 | import com.highcapable.flexilocale.gradle.factory.get 26 | import com.highcapable.flexilocale.gradle.factory.getOrCreate 27 | import com.highcapable.flexilocale.gradle.proxy.IProjectLifecycle 28 | import com.highcapable.flexilocale.plugin.extension.dsl.configure.FlexiLocaleConfigureExtension 29 | import com.highcapable.flexilocale.plugin.helper.LocaleAnalysisHelper 30 | import com.highcapable.flexilocale.utils.debug.FError 31 | import org.gradle.api.Project 32 | 33 | /** 34 | * [FlexiLocale] 插件扩展类 35 | */ 36 | internal class FlexiLocaleExtension internal constructor() : IProjectLifecycle { 37 | 38 | private companion object { 39 | 40 | /** Android Gradle plugin 扩展名称 */ 41 | private const val ANDROID_EXTENSION_NAME = "android" 42 | } 43 | 44 | /** 当前配置方法体实例 */ 45 | private var configure: FlexiLocaleConfigureExtension? = null 46 | 47 | override fun onLoaded(project: Project) { 48 | runCatching { 49 | configure = project.get(ANDROID_EXTENSION_NAME).getOrCreate(FlexiLocaleConfigureExtension.NAME) 50 | }.onFailure { FError.make("Configure $project got an error, ${FlexiLocale.TAG} can only supports Android projects\nCaused by: $it") } 51 | } 52 | 53 | override fun onEvaluate(project: Project) { 54 | val configs = configure?.build(project) ?: FError.make("Extension \"${FlexiLocaleConfigureExtension.NAME}\" create failed") 55 | LocaleAnalysisHelper.start(project, configs) 56 | } 57 | } -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/FlexiLocalePlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/10. 21 | */ 22 | @file:Suppress("unused") 23 | 24 | package com.highcapable.flexilocale.plugin 25 | 26 | import com.highcapable.flexilocale.FlexiLocale 27 | import com.highcapable.flexilocale.utils.debug.FError 28 | import org.gradle.api.Plugin 29 | import org.gradle.api.Project 30 | import org.gradle.api.plugins.ExtensionAware 31 | 32 | /** 33 | * [FlexiLocale] 插件定义类 34 | */ 35 | class FlexiLocalePlugin internal constructor() : Plugin { 36 | 37 | /** 当前扩展实例 */ 38 | private val extension = FlexiLocaleExtension() 39 | 40 | override fun apply(target: T) = when (target) { 41 | is Project -> { 42 | extension.onLoaded(target) 43 | target.afterEvaluate { extension.onEvaluate(project = this) } 44 | } 45 | else -> FError.make("${FlexiLocale.TAG} can only applied in build.gradle or build.gradle.kts, but current is $target") 46 | } 47 | } -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/config/proxy/IFlexiLocaleConfigs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/11. 21 | */ 22 | package com.highcapable.flexilocale.plugin.config.proxy 23 | 24 | import com.highcapable.flexilocale.FlexiLocale 25 | import com.highcapable.flexilocale.generated.FlexiLocaleProperties 26 | 27 | /** 28 | * [FlexiLocale] 配置类接口类 29 | */ 30 | internal interface IFlexiLocaleConfigs { 31 | 32 | companion object { 33 | 34 | /** 35 | * 默认的生成目录路径 36 | * 37 | * "build/generated/[FlexiLocaleProperties.PROJECT_MODULE_NAME]" 38 | */ 39 | internal const val DEFAULT_GENERATE_DIR_PATH = "build/generated/${FlexiLocaleProperties.PROJECT_MODULE_NAME}" 40 | } 41 | 42 | /** 是否启用插件 */ 43 | val isEnable: Boolean 44 | 45 | /** 自定义生成的目录路径 */ 46 | val generateDirPath: String 47 | 48 | /** 自定义生成的包名 */ 49 | val packageName: String 50 | 51 | /** 自定义生成的类名 */ 52 | val className: String 53 | 54 | /** 是否启用受限访问功能 */ 55 | val isEnableRestrictedAccess: Boolean 56 | 57 | /** 58 | * 获取内部 [hashCode] 59 | * @return [Int] 60 | */ 61 | fun innerHashCode() = "$isEnable$generateDirPath$packageName$className$isEnableRestrictedAccess".hashCode() 62 | } -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/extension/dsl/configure/FlexiLocaleConfigureExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/11. 21 | */ 22 | @file:Suppress("MemberVisibilityCanBePrivate") 23 | 24 | package com.highcapable.flexilocale.plugin.extension.dsl.configure 25 | 26 | import com.highcapable.flexilocale.FlexiLocale 27 | import com.highcapable.flexilocale.gradle.factory.fullName 28 | import com.highcapable.flexilocale.plugin.config.proxy.IFlexiLocaleConfigs 29 | import com.highcapable.flexilocale.utils.debug.FError 30 | import com.highcapable.flexilocale.utils.factory.uppercamelcase 31 | import org.gradle.api.Project 32 | 33 | /** 34 | * [FlexiLocale] 配置方法体实现类 35 | */ 36 | open class FlexiLocaleConfigureExtension internal constructor() { 37 | 38 | internal companion object { 39 | 40 | /** [FlexiLocaleConfigureExtension] 扩展名称 */ 41 | internal const val NAME = "flexiLocale" 42 | } 43 | 44 | /** 45 | * 是否启用插件 46 | * 47 | * 默认启用 - 如果你想关闭插件 - 在这里设置就可以了 48 | */ 49 | var isEnable = true 50 | @JvmName("enable") set 51 | 52 | /** 53 | * 自定义生成的目录路径 54 | * 55 | * 你可以填写相对于当前项目的路径 56 | * 57 | * 默认为 [IFlexiLocaleConfigs.DEFAULT_GENERATE_DIR_PATH] 58 | */ 59 | var generateDirPath = IFlexiLocaleConfigs.DEFAULT_GENERATE_DIR_PATH 60 | @JvmName("generateDirPath") set 61 | 62 | /** 63 | * 自定义生成的包名 64 | * 65 | * Android 项目默认使用 "android" 配置方法块中的 "namespace" 66 | */ 67 | var packageName = "" 68 | @JvmName("packageName") set 69 | 70 | /** 71 | * 自定义生成的类名 72 | * 73 | * 默认使用当前项目的名称 + "Locale" 74 | */ 75 | var className = "" 76 | @JvmName("className") set 77 | 78 | /** 79 | * 是否启用受限访问功能 80 | * 81 | * 默认不启用 - 启用后将为生成的类和方法添加 "internal" 修饰符 82 | */ 83 | var isEnableRestrictedAccess = false 84 | @JvmName("enableRestrictedAccess") set 85 | 86 | /** 87 | * 构造 [IFlexiLocaleConfigs] 88 | * @param project 当前项目 89 | * @return [IFlexiLocaleConfigs] 90 | */ 91 | internal fun build(project: Project): IFlexiLocaleConfigs { 92 | /** 检查合法包名 */ 93 | fun String.checkingValidPackageName() { 94 | if (isNotBlank() && !matches("^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*$".toRegex())) 95 | FError.make("Invalid package name \"$this\"") 96 | } 97 | 98 | /** 检查合法类名 */ 99 | fun String.checkingValidClassName() { 100 | if (isNotBlank() && !matches("^[a-zA-Z][a-zA-Z0-9_]*$".toRegex())) 101 | FError.make("Invalid class name \"$this\"") 102 | } 103 | packageName.checkingValidPackageName() 104 | className.checkingValidClassName() 105 | val currentEnable = isEnable 106 | val currentGenerateDirPath = project.file(generateDirPath).absolutePath 107 | val currentPackageName = packageName 108 | val currentClassName = "${className.ifBlank { project.fullName().uppercamelcase() }}Locale" 109 | val currentEnableRestrictedAccess = isEnableRestrictedAccess 110 | return object : IFlexiLocaleConfigs { 111 | override val isEnable get() = currentEnable 112 | override val generateDirPath get() = currentGenerateDirPath 113 | override val packageName get() = currentPackageName 114 | override val className get() = currentClassName 115 | override val isEnableRestrictedAccess get() = currentEnableRestrictedAccess 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/generator/LocaleSourcesGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/10. 21 | */ 22 | package com.highcapable.flexilocale.plugin.generator 23 | 24 | import com.highcapable.flexilocale.FlexiLocale 25 | import com.highcapable.flexilocale.plugin.config.proxy.IFlexiLocaleConfigs 26 | import com.highcapable.flexilocale.plugin.generator.factory.LocaleStringMap 27 | import com.highcapable.flexilocale.utils.debug.FError 28 | import com.highcapable.flexilocale.utils.factory.camelcase 29 | import com.highcapable.flexilocale.utils.factory.uppercamelcase 30 | import com.squareup.kotlinpoet.AnnotationSpec 31 | import com.squareup.kotlinpoet.ClassName 32 | import com.squareup.kotlinpoet.FileSpec 33 | import com.squareup.kotlinpoet.FunSpec 34 | import com.squareup.kotlinpoet.KModifier 35 | import com.squareup.kotlinpoet.LambdaTypeName 36 | import com.squareup.kotlinpoet.ParameterSpec 37 | import com.squareup.kotlinpoet.PropertySpec 38 | import com.squareup.kotlinpoet.TypeSpec 39 | import com.squareup.kotlinpoet.asTypeName 40 | import java.text.SimpleDateFormat 41 | import java.util.* 42 | import kotlin.math.abs 43 | 44 | /** 45 | * I18ns 生成工具类 46 | */ 47 | internal class LocaleSourcesGenerator { 48 | 49 | /** 50 | * 生成 [FileSpec] 51 | * @param configs 当前配置 52 | * @param keyValues 键值数组 53 | * @param namespace 命名空间 54 | * @param packageName 包名 55 | * @return [FileSpec] 56 | * @throws IllegalStateException 如果生成失败 57 | */ 58 | internal fun build( 59 | configs: IFlexiLocaleConfigs, 60 | keyValues: LocaleStringMap, 61 | namespace: String, 62 | packageName: String 63 | ) = runCatching { 64 | FileSpec.builder(packageName, configs.className).apply { 65 | val selfClass = ClassName(packageName, configs.className) 66 | val contextClass = ClassName("android.content", "Context") 67 | val resourcesClass = ClassName("android.content.res", "Resources") 68 | val resourcesInitializer = LambdaTypeName.get(returnType = resourcesClass, parameters = emptyList()) 69 | addAnnotation(AnnotationSpec.builder(Suppress::class).addMember("\"StringFormatInvalid\"").build()) 70 | addImport(namespace, "R") 71 | addType(TypeSpec.classBuilder(selfClass).apply { 72 | addKdoc( 73 | """ 74 | This class is generated by ${FlexiLocale.TAG} at ${SimpleDateFormat.getDateTimeInstance().format(Date())} 75 | 76 | The content here is automatically generated according to the res/values of your projects 77 | 78 | You can visit [here](${FlexiLocale.PROJECT_URL}) for more help 79 | """.trimIndent() 80 | ) 81 | if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL) 82 | addFunction(FunSpec.constructorBuilder().addModifiers(KModifier.PRIVATE).build()) 83 | addProperty(PropertySpec.builder("context", contextClass.copy(nullable = true)).apply { 84 | addKdoc("The current [Context] for this app or library") 85 | addModifiers(KModifier.PRIVATE) 86 | mutable() 87 | initializer("null") 88 | }.build()) 89 | addProperty(PropertySpec.builder("resources", resourcesClass.copy(nullable = true)).apply { 90 | addKdoc("The current [Resources] for this app or library") 91 | addModifiers(KModifier.PRIVATE) 92 | mutable() 93 | initializer("null") 94 | }.build()) 95 | addProperty(PropertySpec.builder("resourcesInitializer", resourcesInitializer.copy(nullable = true)).apply { 96 | addKdoc("The current [Resources] initializer for this app or library") 97 | addModifiers(KModifier.PRIVATE) 98 | mutable() 99 | initializer("null") 100 | }.build()) 101 | addType(TypeSpec.companionObjectBuilder().apply { 102 | if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL) 103 | addFunction(FunSpec.builder("attach").apply { 104 | addKdoc( 105 | """ 106 | Attach [${selfClass.simpleName}] to [Context] 107 | @param context like [android.app.Application] or [android.app.Activity] 108 | @return [${selfClass.simpleName}] 109 | """.trimIndent() 110 | ) 111 | addAnnotation(JvmStatic::class) 112 | if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL) 113 | addParameter("context", contextClass) 114 | addStatement("return ${selfClass.simpleName}().apply { this.context = context }") 115 | returns(selfClass) 116 | }.build()) 117 | addFunction(FunSpec.builder("attach").apply { 118 | addKdoc( 119 | """ 120 | Attach [${selfClass.simpleName}] to [Resources] 121 | 122 | - Note: this method will have no effect if [context] already exists 123 | @param resources A [Resources] that exists and has not been recycled 124 | @return [${selfClass.simpleName}] 125 | """.trimIndent() 126 | ) 127 | addAnnotation(JvmStatic::class) 128 | if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL) 129 | addParameter("resources", resourcesClass) 130 | addStatement("return ${selfClass.simpleName}().apply { this.resources = resources }") 131 | returns(selfClass) 132 | }.build()) 133 | addFunction(FunSpec.builder("attach").apply { 134 | addKdoc( 135 | """ 136 | Attach [${selfClass.simpleName}] to [Resources] initializer 137 | 138 | - Note: this method will have no effect if [context] already exists 139 | @param resourcesInitializer A [Resources] initializer returns a non-recycled instance 140 | @return [${selfClass.simpleName}] 141 | """.trimIndent() 142 | ) 143 | addAnnotation(JvmStatic::class) 144 | if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL) 145 | addParameter("resourcesInitializer", resourcesInitializer) 146 | addStatement("return ${selfClass.simpleName}().apply { this.resourcesInitializer = resourcesInitializer }") 147 | returns(selfClass) 148 | }.build()) 149 | }.build()) 150 | addProperty(PropertySpec.builder("currentResources", resourcesClass).apply { 151 | addKdoc("The current used [Resources] for this app or library") 152 | addModifiers(KModifier.PRIVATE) 153 | getter(FunSpec.getterBuilder().apply { 154 | addStatement("return context?.resources ?: resourcesInitializer?.invoke() ?: resources" + 155 | "?: error(\"${("Unable to get Resource instance, the app may have been killed " + 156 | "or initialization process failed").toKotlinPoetSpace()}\")") 157 | }.build()) 158 | }.build()) 159 | keyValues.forEach { (key, contentValues) -> 160 | val fixedKey = key.camelcase() 161 | val getterKey = "get${key.uppercamelcase()}" 162 | val statement = "return currentResources.getString(R.string.$key, *formatArgs)" 163 | var kDoc = "Resolve the [R.string.$key]\n\n" 164 | if (contentValues.isNotEmpty()) kDoc += "| Configuration | Value |\n| --- | --- |\n" 165 | contentValues.toList() 166 | .sortedWith(compareBy> { it.first != "default" }.thenBy { it.first }) 167 | .toAutoWrapKeyValues() 168 | .forEach { (key, value) -> 169 | val displayValue = value.replace("%".toRegex(), "%%") 170 | kDoc += "| $key | $displayValue |\n" 171 | }; kDoc = kDoc.trim() 172 | addProperty(PropertySpec.builder(fixedKey, String::class).apply { 173 | addKdoc(kDoc) 174 | if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL) 175 | getter(FunSpec.getterBuilder().apply { 176 | addAnnotation(AnnotationSpec.builder(JvmName::class).addMember("\"$getterKey\"").build()) 177 | addStatement("return $fixedKey()") 178 | }.build()) 179 | }.build()) 180 | addFunction(FunSpec.builder(fixedKey).apply { 181 | addKdoc("$kDoc\n@param formatArgs The format arguments that will be used for substitution") 182 | addAnnotation(AnnotationSpec.builder(JvmName::class).addMember("\"$getterKey\"").build()) 183 | if (configs.isEnableRestrictedAccess) addModifiers(KModifier.INTERNAL) 184 | addParameter(ParameterSpec.builder("formatArgs", Any::class.asTypeName()).addModifiers(KModifier.VARARG).build()) 185 | addStatement(statement) 186 | returns(String::class) 187 | }.build()) 188 | } 189 | }.build()) 190 | }.build() 191 | }.getOrElse { FError.make("Failed to generated Kotlin file\n$it") } 192 | 193 | /** 194 | * 转换为自动换行键值对数组 195 | * @return [List]<[Pair]<[String], [String]>> 196 | */ 197 | private fun List>.toAutoWrapKeyValues(): List> { 198 | val maxAllowLength = 75 199 | val punctuations = charArrayOf('.', '。', ',', ',', '、', ';', ';', ':', ':', '!', '!', '?', '?') 200 | val result = mutableListOf>() 201 | val placeholders = mutableListOf>() 202 | forEach { 203 | var key = it.first 204 | var value = it.second.replace("\\n", "ㅤ") 205 | val maxLength = abs(maxAllowLength - key.length) 206 | while (value.length > maxLength) { 207 | var splitIndex = maxLength 208 | var splitValue = value.substring(0, splitIndex) 209 | val lastSpaceIndex = splitValue.lastIndexOf(' ') 210 | val lastPunctuationIndex = splitValue.lastIndexOfAny(punctuations) 211 | val hashWrapIndex = splitValue.lastIndexOf('ㅤ') 212 | when { 213 | hashWrapIndex != -1 && (hashWrapIndex < lastSpaceIndex || hashWrapIndex < lastPunctuationIndex) -> { 214 | splitIndex = hashWrapIndex 215 | splitValue = value.substring(0, splitIndex) 216 | } 217 | lastSpaceIndex != -1 && lastSpaceIndex >= lastPunctuationIndex -> { 218 | splitIndex = lastSpaceIndex + 1 219 | splitValue = value.substring(0, splitIndex) 220 | } 221 | lastPunctuationIndex != -1 -> { 222 | splitIndex = lastPunctuationIndex + 1 223 | splitValue = value.substring(0, splitIndex) 224 | } 225 | } 226 | value = value.substring(splitIndex).trimStart('ㅤ') 227 | result.add(key to splitValue) 228 | key = " ".repeat(key.length) 229 | } 230 | if (value.isNotEmpty()) 231 | result.add(key to value.replace("ㅤ", "")) 232 | else placeholders.add(key to "") 233 | }; result.addAll(placeholders) 234 | return result 235 | } 236 | 237 | /** 238 | * 转换到 KotlinPoet 声明的空格 239 | * @return [String] 240 | */ 241 | private fun String.toKotlinPoetSpace() = replace(" ", "·") 242 | } -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/generator/factory/GeneratorFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/13. 21 | */ 22 | package com.highcapable.flexilocale.plugin.generator.factory 23 | 24 | import java.io.File 25 | 26 | /** I18ns 数组类型定义 */ 27 | internal typealias LocaleStringMap = MutableMap 28 | 29 | /** I18ns (子键值对) 数组类型定义 */ 30 | internal typealias LocaleChildMap = MutableMap 31 | 32 | /** I18ns (文件) 数组类型定义 */ 33 | internal typealias LocaleFileMap = MutableMap> -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/plugin/helper/LocaleAnalysisHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/10. 21 | */ 22 | @file:Suppress("DEPRECATION") 23 | 24 | package com.highcapable.flexilocale.plugin.helper 25 | 26 | import com.android.build.gradle.AppExtension 27 | import com.android.build.gradle.BaseExtension 28 | import com.android.build.gradle.LibraryExtension 29 | import com.android.build.gradle.api.BaseVariant 30 | import com.highcapable.flexilocale.gradle.factory.get 31 | import com.highcapable.flexilocale.plugin.config.proxy.IFlexiLocaleConfigs 32 | import com.highcapable.flexilocale.plugin.generator.LocaleSourcesGenerator 33 | import com.highcapable.flexilocale.plugin.generator.factory.LocaleChildMap 34 | import com.highcapable.flexilocale.plugin.generator.factory.LocaleFileMap 35 | import com.highcapable.flexilocale.plugin.generator.factory.LocaleStringMap 36 | import com.highcapable.flexilocale.utils.debug.FError 37 | import com.highcapable.flexilocale.utils.debug.FLog 38 | import com.highcapable.flexilocale.utils.factory.toFile 39 | import org.gradle.api.Project 40 | import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension 41 | import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension 42 | import org.w3c.dom.Element 43 | import org.w3c.dom.Node 44 | import java.io.File 45 | import javax.xml.parsers.DocumentBuilderFactory 46 | 47 | /** 48 | * I18ns 分析工具类 49 | */ 50 | internal object LocaleAnalysisHelper { 51 | 52 | /** Android 的 Application 插件名称 */ 53 | private const val APPLICATION_PLUGIN_NAME = "com.android.application" 54 | 55 | /** Android 的 Library 插件名称 */ 56 | private const val LIBRARY_PLUGIN_NAME = "com.android.library" 57 | 58 | /** Kotlin 的 Android 插件名称 */ 59 | private const val KT_ANDROID_PLUGIN_NAME = "org.jetbrains.kotlin.android" 60 | 61 | /** I18ns 代码生成实例 */ 62 | private val generator = LocaleSourcesGenerator() 63 | 64 | /** 当前全部 I18ns 数据 (来自但不一定完全为 strings.xml) */ 65 | private val mappedStrings: LocaleStringMap = mutableMapOf() 66 | 67 | /** 当前项目命名空间 */ 68 | private var namespace = "" 69 | 70 | /** 当前项目资源目录数组 */ 71 | private val resDirectories = mutableListOf() 72 | 73 | /** 上次修改的 Hash Code */ 74 | private var lastModifiedHashCode = 0 75 | 76 | /** 配置是否已被修改 */ 77 | private var isConfigsModified = true 78 | 79 | /** 当前使用的配置实例 */ 80 | private lateinit var configs: IFlexiLocaleConfigs 81 | 82 | /** 83 | * 开始分析当前项目 84 | * @param project 当前项目 85 | * @param configs 当前配置 86 | */ 87 | internal fun start(project: Project, configs: IFlexiLocaleConfigs) { 88 | this.configs = configs 89 | if (!configs.isEnable) return 90 | checkingConfigsModified(project, configs) 91 | initializePlugins(project) 92 | val lastMappedStrings: LocaleStringMap = mutableMapOf() 93 | val lastResolveStrings: LocaleStringMap = mutableMapOf() 94 | resDirectories.takeIf { it.isNotEmpty() }?.allValuesDirs()?.forEach { (localeName, files) -> 95 | val stringXmls: LocaleChildMap = mutableMapOf() 96 | files.forEach { stringXmls.putAll(resolveStringXml(it)) } 97 | lastResolveStrings[localeName] = stringXmls 98 | } ?: return FLog.warn( 99 | "Unable to get the resources dir of $project, " + 100 | "please check whether there does not have a resources dir or is not an Android project" 101 | ) 102 | lastResolveStrings.onEach { (localeName, strings) -> 103 | strings.forEach { (key, value) -> 104 | if (lastMappedStrings[key] == null) lastMappedStrings[key] = mutableMapOf() 105 | lastMappedStrings[key]?.set(localeName, value) 106 | } 107 | }.clear() 108 | val isFileModified = mappedStrings != lastMappedStrings 109 | if (!isFileModified && !isConfigsModified) return 110 | mappedStrings.clear() 111 | mappedStrings.putAll(lastMappedStrings) 112 | lastMappedStrings.clear() 113 | updateGeneration() 114 | } 115 | 116 | /** 117 | * 检查配置是否已被修改 118 | * @param project 当前项目 119 | * @param configs 当前配置 120 | */ 121 | private fun checkingConfigsModified(project: Project, configs: IFlexiLocaleConfigs) { 122 | val fileHashCode = project.buildFile.takeIf { it.exists() }?.readText()?.hashCode() ?: -1 123 | isConfigsModified = fileHashCode == -1 || lastModifiedHashCode != fileHashCode || this.configs.innerHashCode() != configs.innerHashCode() 124 | lastModifiedHashCode = fileHashCode 125 | } 126 | 127 | /** 128 | * 初始化 Android Gradle plugin 129 | * @param project 当前项目 130 | */ 131 | private fun initializePlugins(project: Project) { 132 | runCatching { 133 | fun BaseExtension.updateSourceDirs() = sourceSets.configureEach { kotlin.srcDir(configs.generateDirPath) } 134 | fun KotlinProjectExtension.updateSourceDirs() = sourceSets.configureEach { kotlin.srcDir(configs.generateDirPath) } 135 | fun BaseVariant.updateResDirectories() = sourceSets.forEach { provide -> provide.resDirectories?.also { resDirectories.addAll(it) } } 136 | project.plugins.withId(APPLICATION_PLUGIN_NAME) { 137 | project.get().also { extension -> 138 | namespace = extension.namespace ?: "" 139 | extension.applicationVariants.forEach { variant -> 140 | variant.updateResDirectories() 141 | }; extension.updateSourceDirs() 142 | } 143 | } 144 | project.plugins.withId(LIBRARY_PLUGIN_NAME) { 145 | project.get().also { extension -> 146 | namespace = extension.namespace ?: "" 147 | extension.libraryVariants.forEach { variant -> 148 | variant.updateResDirectories() 149 | }; extension.updateSourceDirs() 150 | } 151 | } 152 | project.plugins.withId(KT_ANDROID_PLUGIN_NAME) { 153 | project.get().also { extension -> 154 | extension.updateSourceDirs() 155 | } 156 | } 157 | }.onFailure { FError.make("Failed to initialize Android Gradle plugin, this may be not or a wrong Android project\n$it") } 158 | } 159 | 160 | /** 更新生成后的代码内容 */ 161 | private fun updateGeneration() { 162 | val packageName = "${configs.packageName.ifBlank { namespace }}.generated.locale" 163 | val generateDir = configs.generateDirPath.toFile().apply { if (exists() && isDirectory) deleteRecursively() } 164 | generator.build(configs, mappedStrings, namespace, packageName).writeTo(generateDir) 165 | } 166 | 167 | /** 168 | * 解析当前资源目录下的全部可用 values 目录数组 (包含 I18ns 数据) 169 | * @return [LocaleFileMap] 170 | */ 171 | private fun List.allValuesDirs(): LocaleFileMap { 172 | val valuesDirs: LocaleFileMap = mutableMapOf() 173 | forEach { 174 | it.listFiles()?.filter { dir -> dir.name.startsWith("values") }?.forEach eachDir@{ valuesDir -> 175 | if (!valuesDir.exists() || !valuesDir.isDirectory) return@eachDir 176 | val langName = if (valuesDir.name == "values") "default" else valuesDir.name.split("s-").getOrNull(1) ?: return@eachDir 177 | if (valuesDirs[langName] == null) valuesDirs[langName] = mutableSetOf() 178 | valuesDirs[langName]?.add(valuesDir) 179 | } 180 | }; return valuesDirs 181 | } 182 | 183 | /** 184 | * 解析当前资源目录下的全部 Xml 文件内容到键值对数组 185 | * @param valuesDir 当前资源目录 186 | * @return [LocaleChildMap] 187 | */ 188 | private fun resolveStringXml(valuesDir: File): LocaleChildMap { 189 | val lastMappedStrings: LocaleChildMap = mutableMapOf() 190 | valuesDir.listFiles()?.filter { it.name.endsWith(".xml") }?.forEach { 191 | lastMappedStrings.putAll(it.readText().parseResourcesXml()) 192 | }; return lastMappedStrings 193 | } 194 | 195 | /** 196 | * 解析资源 Xml 文件内容到键值对数组 197 | * @return [LocaleChildMap] 198 | */ 199 | private fun String.parseResourcesXml(): LocaleChildMap { 200 | val builder = DocumentBuilderFactory.newInstance().newDocumentBuilder() 201 | val document = runCatching { builder.parse(byteInputStream()) }.getOrNull() ?: return mutableMapOf() 202 | val rootNode = document.documentElement 203 | if (rootNode.nodeName != "resources") return mutableMapOf() 204 | val nodes = rootNode.getElementsByTagName("string") 205 | val keyValues: LocaleChildMap = mutableMapOf() 206 | (0 until nodes.length).forEach { index -> 207 | val node = nodes.item(index) 208 | if (node.nodeType == Node.ELEMENT_NODE) { 209 | val element = node as Element 210 | val name = element.getAttribute("name") 211 | val content = element.textContent 212 | keyValues[name] = content 213 | } 214 | }; return keyValues 215 | } 216 | } -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/debug/FError.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/10. 21 | */ 22 | package com.highcapable.flexilocale.utils.debug 23 | 24 | import com.highcapable.flexilocale.FlexiLocale 25 | 26 | /** 27 | * 全局异常管理类 28 | */ 29 | internal object FError { 30 | 31 | /** 32 | * 抛出异常 33 | * @param msg 消息内容 34 | * @throws IllegalStateException 35 | */ 36 | internal fun make(msg: String): Nothing = error("[${FlexiLocale.TAG}] $msg") 37 | } -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/debug/FLog.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/10. 21 | */ 22 | @file:Suppress("unused", "MemberVisibilityCanBePrivate") 23 | 24 | package com.highcapable.flexilocale.utils.debug 25 | 26 | import com.highcapable.flexilocale.FlexiLocale 27 | import org.apache.log4j.Logger 28 | 29 | /** 30 | * 全局 Log 管理类 31 | */ 32 | internal object FLog { 33 | 34 | internal const val DONE = "✅" 35 | internal const val IGNORE = "❎" 36 | internal const val ERROR = "❌" 37 | internal const val WARN = "⚠️" 38 | internal const val LINK = "➡️" 39 | internal const val WIRE = "⚙️" 40 | internal const val UP = "⬆️" 41 | internal const val ROTATE = "\uD83D\uDD04" 42 | internal const val ANLZE = "\uD83D\uDD0D" 43 | internal const val STRNG = "\uD83D\uDCAA" 44 | 45 | /** 当前日志输出对象 */ 46 | private val logger = Logger.getLogger(FLog::class.java) 47 | 48 | /** 49 | * 打印 Info (提醒) 级别 Log (绿色) 50 | * @param msg 消息内容 51 | * @param symbol 前缀符号 - 仅限非 [noTag] - 默认无 52 | * @param noTag 无标签 - 默认否 53 | */ 54 | internal fun note(msg: Any, symbol: String = "", noTag: Boolean = false) = 55 | log(if (noTag) msg else msg.createSymbolMsg(symbol), color = "38;5;10") 56 | 57 | /** 58 | * 打印 Info 级别 Log (无颜色) 59 | * @param msg 消息内容 60 | * @param symbol 前缀符号 - 仅限非 [noTag] - 默认无 61 | * @param noTag 无标签 - 默认否 62 | */ 63 | internal fun info(msg: Any, symbol: String = "", noTag: Boolean = false) = 64 | log(if (noTag) msg else msg.createSymbolMsg(symbol)) 65 | 66 | /** 67 | * 打印 Warn 级别 Log (黄色) 68 | * @param msg 消息内容 69 | * @param symbol 前缀符号 - 仅限非 [noTag] - 默认 [WARN] 70 | * @param noTag 无标签 - 默认否 71 | */ 72 | internal fun warn(msg: Any, symbol: String = WARN, noTag: Boolean = false) = 73 | log(if (noTag) msg else msg.createSymbolMsg(symbol), color = "33") 74 | 75 | /** 76 | * 打印 Error 级别 Log (红色) 77 | * @param msg 消息内容 78 | * @param symbol 前缀符号 - 仅限非 [noTag] - 默认 [ERROR] 79 | * @param noTag 无标签 - 默认否 80 | */ 81 | internal fun error(msg: Any, symbol: String = ERROR, noTag: Boolean = false) = 82 | log(if (noTag) msg else msg.createSymbolMsg(symbol), isError = true) 83 | 84 | /** 85 | * 创建符号消息内容 86 | * @param symbol 前缀符号 87 | * @return [String] 88 | */ 89 | private fun Any.createSymbolMsg(symbol: String) = 90 | if (symbol.isNotBlank()) "[${FlexiLocale.TAG}] $symbol $this" else "[${FlexiLocale.TAG}] $this" 91 | 92 | /** 93 | * 打印 Log 94 | * @param msg 消息内容 95 | * @param color 颜色代码 - 默认无颜色 96 | * @param isError 是否强制为错误日志 - 默认否 97 | */ 98 | private fun log(msg: Any, color: String = "0", isError: Boolean = false) = when { 99 | isError -> logger.error(msg) 100 | color != "0" -> println("\u001B[${color}m$msg\u001B[0m") 101 | else -> println(msg) 102 | } 103 | } -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/factory/FileFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/11. 21 | */ 22 | @file:Suppress("unused") 23 | 24 | package com.highcapable.flexilocale.utils.factory 25 | 26 | import java.io.File 27 | 28 | /** 29 | * 字符串路径转换为文件 30 | * 31 | * 自动调用 [parseFileSeparator] 32 | * @return [File] 33 | */ 34 | internal fun String.toFile() = File(parseFileSeparator()) 35 | 36 | /** 37 | * 格式化到当前操作系统的文件分隔符 38 | * @return [String] 39 | */ 40 | internal fun String.parseFileSeparator() = replace("/", File.separator).replace("\\", File.separator) -------------------------------------------------------------------------------- /flexilocale-gradle-plugin/src/main/kotlin/com/highcapable/flexilocale/utils/factory/VariableFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * FlexiLocale - An easy generation Android i18ns string call Gradle plugin. 3 | * Copyright (C) 2019 HighCapable 4 | * https://github.com/BetterAndroid/FlexiLocale 5 | * 6 | * Apache License Version 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * This file is created by fankes on 2023/10/10. 21 | */ 22 | package com.highcapable.flexilocale.utils.factory 23 | 24 | /** 25 | * 下划线、分隔线、点、冒号、空格命名字符串转小驼峰命名字符串 26 | * @return [String] 27 | */ 28 | internal fun String.camelcase() = runCatching { 29 | split("_", ".", "-", ":", " ").map { it.replaceFirstChar { e -> e.titlecase() } }.let { words -> 30 | words.first().replaceFirstChar { it.lowercase() } + words.drop(1).joinToString("") 31 | } 32 | }.getOrNull() ?: this 33 | 34 | /** 35 | * 下划线、分隔线、点、空格命名字符串转大驼峰命名字符串 36 | * @return [String] 37 | */ 38 | internal fun String.uppercamelcase() = camelcase().capitalize() 39 | 40 | /** 41 | * 字符串首字母大写 42 | * @return [String] 43 | */ 44 | internal fun String.capitalize() = replaceFirstChar { it.uppercaseChar() } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project Configuration 2 | project.name=FlexiLocale 3 | project.url=https://github.com/BetterAndroid/FlexiLocale 4 | project.groupName=com.highcapable.flexilocale 5 | project.moduleName=flexi-locale 6 | project.version=1.0.1 7 | # Gradle Plugin Configuration 8 | gradle.plugin.moduleName=${project.groupName}.gradle.plugin 9 | gradle.plugin.implementationClass=${project.groupName}.plugin.FlexiLocalePlugin 10 | # Maven Publish Configuration 11 | SONATYPE_HOST=S01 12 | RELEASE_SIGNING_ENABLED=true 13 | # Maven POM Configuration 14 | POM_NAME=FlexiLocale 15 | POM_ARTIFACT_ID=flexi-locale 16 | POM_DESCRIPTION=An easy generation Android i18ns string call Gradle plugin. 17 | POM_URL=https://github.com/BetterAndroid/FlexiLocale 18 | POM_LICENSE_NAME=Apache License 2.0 19 | POM_LICENSE_URL=https://github.com/BetterAndroid/FlexiLocale/blob/master/LICENSE 20 | POM_LICENSE_DIST=repo 21 | POM_SCM_URL=https://github.com/BetterAndroid/FlexiLocale 22 | POM_SCM_CONNECTION=scm:git:git://github.com/BetterAndroid/FlexiLocale.git 23 | POM_SCM_DEV_CONNECTION=scm:git:ssh://github.com/BetterAndroid/FlexiLocale.git 24 | POM_DEVELOPER_ID=0 25 | POM_DEVELOPER_NAME=fankes 26 | POM_DEVELOPER_EMAIL=qzmmcn@163.com 27 | POM_DEVELOPER_URL=https://github.com/fankes -------------------------------------------------------------------------------- /gradle/sweet-dependency/sweet-dependency-config.yaml: -------------------------------------------------------------------------------- 1 | preferences: 2 | autowire-on-sync-mode: UPDATE_OPTIONAL_DEPENDENCIES 3 | repositories-mode: FAIL_ON_PROJECT_REPOS 4 | 5 | repositories: 6 | gradle-plugin-portal: 7 | scope: PLUGINS 8 | google: 9 | maven-central: 10 | 11 | plugins: 12 | org.jetbrains.kotlin.jvm: 13 | alias: kotlin-jvm 14 | version: 2.1.10 15 | com.vanniktech.maven.publish: 16 | alias: maven-publish 17 | version: 0.31.0 18 | 19 | libraries: 20 | com.android.library: 21 | com.android.library.gradle.plugin: 22 | version: 8.9.0 23 | org.jetbrains.kotlin: 24 | kotlin-gradle-plugin: 25 | version: 2.1.10 26 | com.squareup: 27 | kotlinpoet: 28 | version: 2.1.0 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BetterAndroid/FlexiLocale/56ff6a109aaecb851b09c82b6f4170a3c33fad10/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /img-src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BetterAndroid/FlexiLocale/56ff6a109aaecb851b09c82b6f4170a3c33fad10/img-src/icon.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | plugins { 9 | id("com.highcapable.sweetdependency") version "1.0.4" 10 | id("com.highcapable.sweetproperty") version "1.0.5" 11 | } 12 | sweetDependency { 13 | isEnableVerboseMode = false 14 | } 15 | sweetProperty { 16 | global { 17 | sourcesCode { 18 | className = rootProject.name 19 | includeKeys( 20 | "^project\\..*\$".toRegex(), 21 | "^gradle\\..*\$".toRegex() 22 | ) 23 | isEnableRestrictedAccess = true 24 | } 25 | } 26 | rootProject { all { isEnable = false } } 27 | } 28 | rootProject.name = "FlexiLocale" 29 | include(":flexilocale-gradle-plugin") --------------------------------------------------------------------------------