├── .gitattributes ├── .github └── workflows │ ├── codeql-analysis.yml │ ├── gradle.yml │ └── release.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md └── TransifexNativeSDK ├── .gitignore ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── example.html │ ├── java │ └── com │ │ └── transifex │ │ └── myapplication │ │ ├── MainActivity.java │ │ ├── MyApplication.java │ │ ├── SimpleIntentService.java │ │ └── WebViewActivity.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── ic_launcher_background.xml │ ├── state_list_icon.xml │ └── vector_android.xml │ ├── layout │ ├── activity_main.xml │ └── activity_web_view.xml │ ├── menu │ └── main_menu.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values-el │ └── strings.xml │ ├── values-night │ └── styles.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── clitool ├── .gitignore ├── build.gradle ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── transifex │ │ │ └── clitool │ │ │ ├── MainClass.java │ │ │ └── StringXMLConverter.java │ └── test │ │ └── java │ │ └── com │ │ └── transifex │ │ └── clitool │ │ ├── MainClassTest.java │ │ └── StringXMLConverterTest.java └── testFiles │ ├── strings-test-at.xml │ ├── strings-test-backslash.xml │ ├── strings-test-htmlentities.xml │ ├── strings-test-insidedoublequotes.xml │ ├── strings-test-new-line-space-tab.xml │ ├── strings-test-new-line.xml │ ├── strings-test-quote.xml │ ├── strings-test-space.xml │ ├── strings-test-tab.xml │ └── strings-test-unicode.xml ├── common ├── .gitignore ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── transifex │ │ └── common │ │ ├── CDSHandler.java │ │ ├── LocaleData.java │ │ ├── Plurals.java │ │ ├── TranslationMapStorage.java │ │ ├── TranslationsDownloader.java │ │ └── Utils.java │ ├── test │ └── java │ │ └── com │ │ └── transifex │ │ └── common │ │ ├── CDSHandlerTest.java │ │ ├── LocaleDataTest.java │ │ ├── PluralsTest.java │ │ ├── TranslationMapStorageTest.java │ │ └── TranslationsDownloaderTest.java │ └── testFixtures │ └── java │ └── com │ └── transifex │ └── common │ ├── CDSMockHelper.java │ ├── StringTestData.java │ └── TempDirHelper.java ├── doc └── readme.html ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── publish-helper.gradle ├── settings.gradle └── txsdk ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src ├── androidTest ├── assets │ ├── test_fileInsteadLocaleDir │ │ └── dummy_file │ ├── test_normal │ │ ├── el │ │ │ └── strings.txt │ │ └── es │ │ │ └── strings.txt │ ├── test_oneLocaleFileIsMissing │ │ ├── el │ │ │ └── strings.txt │ │ └── es │ │ │ └── wrong_file │ ├── test_oneLocaleHasInvalidJson │ │ ├── el │ │ │ └── strings.txt │ │ └── es │ │ │ └── strings.txt │ └── txnative │ │ ├── el │ │ └── txstrings.json │ │ └── es │ │ └── txstrings.json └── java │ └── com │ └── transifex │ └── txnative │ ├── PluralsTest.java │ ├── TranslationMapStorageAndroidTest.java │ ├── TranslationMapStorageTest.java │ └── cache │ ├── TxDiskTranslationsProviderAssetsTest.java │ └── TxStandardCacheTest.java ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── transifex │ │ └── txnative │ │ ├── CDSHandlerAndroid.java │ │ ├── LocaleState.java │ │ ├── NativeCore.java │ │ ├── TranslationMapStorageAndroid.java │ │ ├── TxInterceptor.java │ │ ├── TxNative.java │ │ ├── TxResources.java │ │ ├── Utils.java │ │ ├── activity │ │ ├── TxBaseActivity.java │ │ └── TxBaseAppCompatActivity.java │ │ ├── cache │ │ ├── TxCache.java │ │ ├── TxDecoratorCache.java │ │ ├── TxDiskTranslationsProvider.java │ │ ├── TxFileOutputCacheDecorator.java │ │ ├── TxMemoryCache.java │ │ ├── TxProviderBasedCache.java │ │ ├── TxReadonlyCacheDecorator.java │ │ ├── TxStandardCache.java │ │ ├── TxTranslationsProvider.java │ │ └── TxUpdateFilterCache.java │ │ ├── missingpolicy │ │ ├── AndroidMissingPolicy.java │ │ ├── CompositeMissingPolicy.java │ │ ├── MissingPolicy.java │ │ ├── PseudoTranslationPolicy.java │ │ ├── SourceStringPolicy.java │ │ └── WrappedStringPolicy.java │ │ ├── transformers │ │ ├── SupportToolbarTransformer.java │ │ ├── TextInputLayoutTransformer.java │ │ ├── TextViewTransformer.java │ │ ├── ToolbarTransformer.java │ │ └── ViewTransformer.java │ │ └── wrappers │ │ ├── ResourcesWrapper.java │ │ ├── TxContextWrapper.java │ │ ├── TxContextWrappingDelegate2.kt │ │ ├── TxContextWrappingDelegateJava2.java │ │ └── VectorEnabledTintResourcesWrapper.java └── res │ └── values │ └── strings.xml └── test ├── java └── com │ └── transifex │ └── txnative │ ├── LocaleStateTest.java │ ├── NativeCoreTest.java │ ├── TxResourcesTest.java │ ├── cache │ ├── TxDecoratorCacheTest.java │ ├── TxDiskTranslationsProviderFilesTest.java │ ├── TxFileOutputCacheDecoratorTest.java │ ├── TxMemoryCacheTest.java │ ├── TxProviderBasedCacheTest.java │ ├── TxReadonlyCacheDecoratorTest.java │ └── TxUpdateFilterCacheTest.java │ └── missingpolicy │ ├── AndroidMissingPolicyTest.java │ ├── CompositeMissingPolicyTest.java │ ├── PseudoTranslationPolicyTest.java │ ├── SourceStringPolicyTest.java │ └── WrappedStringPolicyTest.java └── res ├── values-el └── strings.xml ├── values-es └── strings.xml └── values └── strings.xml /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.jar binary 3 | *.png binary 4 | *.jpg binary 5 | *.keystore binary 6 | *.exe binary -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "devel" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "devel" ] 20 | schedule: 21 | - cron: '25 12 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: [ devel ] 9 | pull_request: 10 | branches: [ devel ] 11 | 12 | jobs: 13 | test: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Set up JDK 17 20 | uses: actions/setup-java@v3 21 | with: 22 | java-version: '17' 23 | distribution: 'temurin' 24 | architecture: x64 25 | - name: Set up Gradle 26 | uses: gradle/gradle-build-action@v2 27 | with: 28 | # Only write to the cache for builds on the 'main' and 'devel' branches. (Default is 'main' only.) 29 | # Builds on other branches will only read existing entries from the cache. 30 | cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/devel' }} 31 | - name: Grant execute permission for gradlew 32 | working-directory: ./TransifexNativeSDK 33 | run: chmod +x gradlew 34 | - name: Unit Test 35 | working-directory: ./TransifexNativeSDK 36 | run: ./gradlew test 37 | - name: Upload test reports 38 | if: always() 39 | uses: actions/upload-artifact@v3 40 | with: 41 | name: unitTestReports 42 | path: | 43 | TransifexNativeSDK/clitool/build/reports/tests/test/ 44 | TransifexNativeSDK/common/build/reports/tests/test/ 45 | TransifexNativeSDK/txsdk/build/reports/tests/ 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow is triggered by new releases and performes these steps: 2 | # * it builds and uploads the CLI tool as an artifact to the release that triggered it 3 | # * it generates and deploys the documentation to Github pages and it builds and 4 | # publishes the sdk to Maven. 5 | # If the tag ends with "SNAPSHOT", only publishing to Maven takes place. 6 | 7 | name: Release 8 | 9 | on: 10 | release: 11 | types: [published] 12 | 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Set up JDK 17 22 | uses: actions/setup-java@v3 23 | with: 24 | java-version: '17' 25 | distribution: 'temurin' 26 | architecture: x64 27 | - name: Set up Gradle 28 | uses: gradle/gradle-build-action@v2 29 | with: 30 | # Only write to the cache for builds on the 'main' and 'devel' branches. (Default is 'main' only.) 31 | # Builds on other branches will only read existing entries from the cache. 32 | cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/devel' }} 33 | - name: Grant execute permission for gradlew 34 | working-directory: ./TransifexNativeSDK 35 | run: chmod +x gradlew 36 | 37 | - name: Assemble clitool jar 38 | if: ${{ !endsWith(github.event.release.tag_name, 'SNAPSHOT') }} 39 | working-directory: ./TransifexNativeSDK 40 | run: ./gradlew clitool:assemble 41 | - name : Upload clitool's jar to release 42 | if: ${{ !endsWith(github.event.release.tag_name, 'SNAPSHOT') }} 43 | uses: actions/upload-release-asset@v1.0.2 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | with: 47 | upload_url: ${{ github.event.release.upload_url }} 48 | asset_name: 'transifex.jar' 49 | asset_path: TransifexNativeSDK/clitool/build/libs/transifex.jar 50 | asset_content_type: application/java-archive 51 | 52 | - name: Generate Javadoc 53 | if: ${{ !endsWith(github.event.release.tag_name, 'SNAPSHOT') }} 54 | working-directory: ./TransifexNativeSDK 55 | run: ./gradlew aggregatedJavadoc 56 | - name: Deploy to GitHub Pages 57 | if: ${{ !endsWith(github.event.release.tag_name, 'SNAPSHOT') }} 58 | uses: JamesIves/github-pages-deploy-action@4.1.1 59 | with: 60 | branch: documentation 61 | folder: TransifexNativeSDK/build/docs/javadoc 62 | target-folder: docs 63 | 64 | - name: Build and publish SDK to Maven 65 | working-directory: ./TransifexNativeSDK 66 | env: 67 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} 68 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} 69 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 70 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 71 | PGP_KEY_CONTENTS: ${{ secrets.PGP_KEY_CONTENTS }} 72 | run: | 73 | ./gradlew publishReleasePublicationToSonatypeRepository closeAndReleaseSonatypeStagingRepository 74 | ./gradlew cleanTmp 75 | 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Android template 2 | # Built application files 3 | *.apk 4 | *.ap_ 5 | 6 | # Files for the Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # lint files 13 | lint.xml 14 | 15 | # proguard files 16 | seeds.txt 17 | unused.txt 18 | mapping.txt 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | !gradle-wrapper.jar 24 | 25 | # generated files 26 | bin/ 27 | gen/ 28 | 29 | # Android Studio 30 | **/.idea/* 31 | !**/.idea/scopes/ 32 | *.ipr 33 | *.iws 34 | *.iml 35 | 36 | # Local configuration file (sdk path, etc) 37 | local.properties 38 | 39 | # Log Files 40 | *.log -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at support@transifex.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Transifex Native Android SDK 2 | 3 | :tada: Thank you for your interest in contributing to this project! :tada: 4 | 5 | ## Table Of Contents 6 | 7 | * [Code of Conduct](#code-of-conduct) 8 | * [I just have a question](#i-just-have-a-question) 9 | * [Contributor License Agreement](#contributor-license-agreement) 10 | * [How to Contribute](#how-to-contribute) 11 | * [Reporting Bugs](#reporting-bugs) 12 | * [Suggesting Improvements](#suggesting-improvements) 13 | * [Creating Patches](#creating-patches) 14 | 15 | # Code of Conduct 16 | 17 | Please read this project's [Code of Conduct](/CODE_OF_CONDUCT.md) for the detailed standards on how to engage in this community. 18 | 19 | # I just have a question 20 | 21 | If you are not looking to contribute, but rather have a question about this project, you can visit the [Transifex Community](https://community.transifex.com/c/transifex-native) and ask there directly. 22 | 23 | # How to Contribute 24 | 25 | There are several ways to contribute to this project and they are all equally welcome. You can report a bug, suggest an improvement or submit an improvement yourself, be it a change in the code or in the documentation. 26 | 27 | ## Reporting Bugs 28 | 29 | Bugs in this project are tracked as [GitHub issues](https://guides.github.com/features/issues/). Before submitting a new issue, make sure you check the [list of exiting issues](https://github.com/transifex/transifex-java/issues), as it might already be there. 30 | 31 | When creating an issue, make sure you include the following: 32 | * A clear and descriptive title. 33 | * The exact steps to allow others to reproduce the issue. 34 | * The behavior you observed and the reason you find this problematic. 35 | * The behavior you expected instead and the reason for this. 36 | * Any material that would better showcase the issue, such as screenshots or animated GIFs. 37 | * The version(s) of this library you have seen this erroneous behavior in 38 | * Make sure you DO NOT INCLUDE any sensitive information, such as a token, a secret or a private URL to a Transifex project or resource 39 | 40 | ## Suggesting Improvements 41 | 42 | Improvement suggestions in this project are tracked as [GitHub issues](https://guides.github.com/features/issues/). Before submitting a new suggestion, make sure you check the [list of exiting issues](https://github.com/transifex/transifex-java/issues), as it might already be there. 43 | 44 | When creating an improvement issue, make sure you include the following: 45 | * A clear and descriptive title. 46 | * The exact steps to allow others to reproduce the issue. 47 | * The current behavior/functionality, what you would prefer to see instead and why. 48 | * An explanation on why this improvement would benefit most users of this library and not just your use case 49 | * Any material that could better showcase the current and the expected behavior, such as screenshots or animated GIFs. 50 | * Make sure you DO NOT INCLUDE any sensitive information, such as a token, a secret or a private URL to a Transifex project or resource 51 | 52 | ## Creating Patches 53 | 54 | ### Contributor License Agreement 55 | 56 | Before contributing with suggested changes, you must sign our [Contributor License Agreement](https://developers.transifex.com/docs/contributing). 57 | 58 | If you want to submit a patch that either fixes a bug or introduces a new feature, you need to have the following in mind: 59 | * The Pull Request (PR) handles one thing, i.e. fixes a certain bug or introduces a specific new functionality, instead of combining many at once. 60 | * Each commit should include one logical change each and should ideally be few in number per PR. 61 | * All [status checks](https://help.github.com/articles/about-status-checks), including the execution of the test suit, must pass. 62 | * The Rebase workflow is followed: each branch must be rebased over `origin/master` before being allowed to get merged. 63 | 64 | In order for a PR to be merged, at least one maintainer of this project must first review and approve it. The reviewer(s) may request changes before moving on. 65 | -------------------------------------------------------------------------------- /TransifexNativeSDK/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | /tmp 16 | -------------------------------------------------------------------------------- /TransifexNativeSDK/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /TransifexNativeSDK/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | 7 | namespace 'com.transifex.myapplication' 8 | 9 | defaultConfig { 10 | applicationId "com.transifex.myapplication" 11 | minSdkVersion 18 12 | compileSdk 33 13 | targetSdkVersion 33 14 | versionCode 1 15 | versionName "1.0" 16 | // https://stackoverflow.com/a/41345440/941314 17 | // https://developer.android.com/build/shrink-code#unused-alt-resources 18 | // Needed for multilingual-support after Android N 19 | resourceConfigurations += ['en', 'el', 'de', 'fr', 'ar', 'sl'] 20 | 21 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 22 | } 23 | 24 | buildTypes { 25 | minifyTesting { 26 | initWith(buildTypes["debug"]) 27 | minifyEnabled true 28 | debuggable false 29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 30 | matchingFallbacks = ['release'] 31 | } 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | 38 | compileOptions { 39 | sourceCompatibility JavaVersion.VERSION_1_8 40 | targetCompatibility JavaVersion.VERSION_1_8 41 | } 42 | } 43 | 44 | // Uncomment in combination with "setCompatVectorFromResourcesEnabled()" in "MyApplication" to 45 | // check the workaround in TxBaseAppCompatActivity. 46 | //configurations.all { 47 | // resolutionStrategy { 48 | // force 'androidx.appcompat:appcompat:1.3.0' 49 | // } 50 | //} 51 | 52 | dependencies { 53 | implementation fileTree(dir: "libs", include: ["*.jar"]) 54 | implementation "androidx.appcompat:appcompat:$versions.appcompat" 55 | implementation "com.google.android.material:material:$versions.material" 56 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 57 | 58 | testImplementation "junit:junit:$versions.junit" 59 | 60 | androidTestImplementation "androidx.test.ext:junit:$versions.androidXJunit" 61 | androidTestImplementation "androidx.test.espresso:espresso-core:$versions.androidxEspressoCore" 62 | 63 | implementation project(':txsdk') 64 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | 16 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/assets/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Example

4 | 11 | -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/java/com/transifex/myapplication/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.transifex.myapplication; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.text.Spanned; 6 | import android.util.TypedValue; 7 | import android.view.Menu; 8 | import android.view.MenuInflater; 9 | import android.view.View; 10 | import android.widget.Button; 11 | import android.widget.TextView; 12 | 13 | import com.transifex.txnative.activity.TxBaseAppCompatActivity; 14 | 15 | import androidx.appcompat.widget.Toolbar; 16 | import androidx.core.text.HtmlCompat; 17 | 18 | 19 | // Extend your activity from BaseAppCompatActivity or BaseActivity 20 | public class MainActivity extends TxBaseAppCompatActivity { 21 | 22 | public static final String TAG = MainActivity.class.getSimpleName(); 23 | 24 | Toolbar mToolbar; 25 | TextView mHelloLabel; 26 | TextView mWelcomeLabel; 27 | TextView mArrayLabel; 28 | TextView mPluralLabel; 29 | TextView mFormatLabel; 30 | TextView mStyledLabel; 31 | TextView mReferenceUseLabel; 32 | Button mButton; 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_main); 38 | 39 | mToolbar = findViewById(R.id.toolbar); 40 | setSupportActionBar(mToolbar); 41 | 42 | mHelloLabel = findViewById(R.id.hello_label); 43 | 44 | // In this activity, we make use of all the different type of string methods supported by 45 | // Android: https://developer.android.com/guide/topics/resources/string-resource 46 | 47 | // Simple text 48 | mWelcomeLabel = findViewById(R.id.welcome_label); 49 | mWelcomeLabel.setText(getString(R.string.welcome_text)); 50 | //mWelcomeLabel.setText(R.string.welcome_text); // this also works 51 | 52 | // String array [not supported at the moment] 53 | mArrayLabel = findViewById(R.id.array_label); 54 | { 55 | String[] strings = getResources().getStringArray(R.array.elements_array); 56 | StringBuilder sb = new StringBuilder(); 57 | for (String segment : strings) { 58 | sb.append(segment); 59 | sb.append(" "); 60 | } 61 | mArrayLabel.setText(sb.toString()); 62 | } 63 | 64 | // Quantity strings 65 | mPluralLabel = findViewById(R.id.plural_label); 66 | { 67 | String pluralString = getResources().getQuantityString(R.plurals.duration_seconds, 2, 2); 68 | mPluralLabel.setText(pluralString); 69 | } 70 | 71 | // Formatted string 72 | mFormatLabel = findViewById(R.id.formatted_label); 73 | { 74 | String formattedString = getResources().getString(R.string.hello_user, "John", 2); 75 | mFormatLabel.setText(formattedString); 76 | } 77 | 78 | // Styled text 79 | mStyledLabel = findViewById(R.id.styled_label); 80 | { 81 | String string = getResources().getString(R.string.styled_text); 82 | Spanned styledText = HtmlCompat.fromHtml(string, HtmlCompat.FROM_HTML_MODE_COMPACT); 83 | mStyledLabel.setText(styledText); 84 | } 85 | 86 | // Themed attribute referencing string resource 87 | mReferenceUseLabel = findViewById(R.id.reference_use); 88 | { 89 | TypedValue typedValue = new TypedValue(); 90 | getTheme().resolveAttribute(R.attr.theme_string, typedValue, true); 91 | mReferenceUseLabel.setText(typedValue.resourceId); 92 | } 93 | 94 | mButton = findViewById(R.id.button); 95 | mButton.setOnClickListener(new View.OnClickListener() { 96 | @Override 97 | public void onClick(View v) { 98 | startActivity(new Intent(MainActivity.this, WebViewActivity.class)); 99 | } 100 | }); 101 | 102 | } 103 | 104 | @Override 105 | public boolean onCreateOptionsMenu(Menu menu) { 106 | MenuInflater inflater = getMenuInflater(); 107 | inflater.inflate(R.menu.main_menu, menu); 108 | 109 | return true; 110 | } 111 | 112 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/java/com/transifex/myapplication/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.transifex.myapplication; 2 | 3 | import android.app.Application; 4 | import android.content.Intent; 5 | 6 | import com.transifex.txnative.LocaleState; 7 | import com.transifex.txnative.TxNative; 8 | 9 | public class MyApplication extends Application { 10 | 11 | @Override 12 | public void onCreate() { 13 | super.onCreate(); 14 | 15 | // Uncomment to test vector resources when running on older platforms (< API 21) 16 | // AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); 17 | 18 | // Uncomment to test if everything works when AppCompat changes the resources to force 19 | // night mode: 20 | // https://stackoverflow.com/questions/55265834/change-locale-not-work-after-migrate-to-androidx/58004553#58004553 21 | // AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); 22 | 23 | // Initialize TxNative 24 | String token = null; 25 | 26 | // The app locales entered here should match the ones in `resConfigs` in gradle, so that 27 | // multi locale support works for newer Androids. 28 | LocaleState localeState = new LocaleState(getApplicationContext(), 29 | "en", 30 | new String[]{"en", "el", "de", "fr", "ar", "sl"}, 31 | null); 32 | TxNative.init( 33 | getApplicationContext(), // application context 34 | localeState, // a LocaleState instance 35 | token, // token 36 | null, // cdsHost URL 37 | null, // a TxCache implementation 38 | null); // a MissingPolicy implementation 39 | 40 | // Uncomment to use strings as served by Android prefixed with "test: " 41 | // TxNative.setTestMode(true); 42 | 43 | // Uncomment, to disable styling of strings with HTML markup such as 44 | // R.string.styled_text_not_escaped 45 | // TxNative.setSupportSpannable(false); 46 | 47 | // Fetch all translations from CDS 48 | TxNative.fetchTranslations(null, null); 49 | 50 | // Start a service just for testing purposes 51 | Intent serviceIntent = new Intent(this, SimpleIntentService.class); 52 | SimpleIntentService.enqueueWork(this, serviceIntent); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/java/com/transifex/myapplication/SimpleIntentService.java: -------------------------------------------------------------------------------- 1 | package com.transifex.myapplication; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.util.Log; 6 | 7 | import com.transifex.txnative.TxNative; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.core.app.JobIntentService; 11 | 12 | /** 13 | * A JobIntentService that demonstrates how to use TransifexNative in services. 14 | */ 15 | public class SimpleIntentService extends JobIntentService { 16 | 17 | public static final String TAG = SimpleIntentService.class.getSimpleName(); 18 | 19 | static final int JOB_ID = 1000; 20 | 21 | public static void enqueueWork(Context context, Intent work) { 22 | enqueueWork(context, SimpleIntentService.class, JOB_ID, work); 23 | } 24 | 25 | @Override 26 | protected void onHandleWork(@NonNull Intent intent) { 27 | // Make sure that you use getBaseContext() and not getApplicationContext() 28 | String success = getBaseContext().getResources().getString(R.string.success); 29 | 30 | Log.d(TAG, "Service status: " + success); 31 | } 32 | 33 | @Override 34 | protected void attachBaseContext(Context newBase) { 35 | // Wrap the base context 36 | super.attachBaseContext(TxNative.wrap(newBase)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/java/com/transifex/myapplication/WebViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.transifex.myapplication; 2 | 3 | import android.os.Bundle; 4 | import android.webkit.WebView; 5 | 6 | import com.transifex.txnative.activity.TxBaseAppCompatActivity; 7 | 8 | public class WebViewActivity extends TxBaseAppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_web_view); 14 | 15 | ((WebView)findViewById(R.id.webview)).loadUrl("file:///android_asset/example.html"); 16 | } 17 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/drawable/state_list_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/drawable/vector_android.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/layout/activity_web_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 23 | 24 | 25 | 26 | 35 | 36 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/values-el/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Transifex Application ελ 4 | 5 | Transifex demo activity ελ 6 | 7 | Transifex SDK ελ 8 | String demo ελ 9 | 10 | Hello World! ελ 11 | 12 | Welcome to our demo ελ 13 | 14 | 15 | 1st element ελ 16 | 2nd element ελ 17 | 3rd element ελ 18 | 19 | 20 | 21 | %d second ελ 22 | %d seconds ελ 23 | 24 | 25 | Hello, %1$s! You have %2$d new messages. ελ 26 | 27 | A <font color="#FF7700">localization</font> platform <u>that</u> moves as <b>fast</b> as <big>you</big> do\n https://www.transifex.com/ ελ 28 | 29 | This is blue ελ 30 | 31 | Describe the problem in detail * ελ 32 | 33 | Dark theme ελ 34 | 35 | Light theme ελ 36 | 37 | Press me ελ 38 | 39 | This is an image ελ 40 | 41 | This is a tooltip ελ 42 | 43 | Type here ελ 44 | 45 | Settings ελ 46 | 47 | success ελ 48 | 49 | -------------------------------------------------------------------------------- /TransifexNativeSDK/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /TransifexNativeSDK/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | ext.versions = [ 4 | 'androidXAnnotation' : '1.7.0', 5 | 'appcompat' : '1.6.1', 6 | 'material' : '1.9.0', 7 | 'truth' : '1.1.3', 8 | 'gson' : '2.9.1', 9 | 'junit' : '4.13.2', 10 | 'androidXTestCore' : '1.5.0', 11 | 'roboelectric' : '4.10.3', 12 | 'mockitoCore' : '5.5.0', 13 | 'okhttp3Mockwebserver' : '4.10.0', 14 | 'androidXJunit' : '1.1.5', 15 | 'androidxEspressoCore' : '3.5.1' 16 | ] 17 | ext { 18 | sdkVersionCode = 10 // version code for txsdk 19 | sdkVersion = '1.3.0' // version for txsdk and common 20 | pomGroupID = "com.transifex.txnative" // pom group id for txsdk and common 21 | 22 | cliVersion = '1.3.0' // clitool version 23 | } 24 | repositories { 25 | google() 26 | mavenCentral() 27 | } 28 | dependencies { 29 | // NOTE: Do not place your application dependencies here; they belong 30 | // in the individual module build.gradle files 31 | } 32 | } 33 | 34 | plugins { 35 | id "io.github.gradle-nexus.publish-plugin" version "1.3.0" 36 | id 'com.android.application' version '8.1.0' apply false 37 | id 'com.android.library' version '8.1.0' apply false 38 | id 'org.jetbrains.kotlin.android' version '1.9.20' apply false 39 | } 40 | 41 | // Initialize publishing/signing extra properties with environmental vars 42 | ext['signing.keyId'] = System.getenv('SIGNING_KEY_ID') ?: '' 43 | ext['signing.password'] = System.getenv('SIGNING_PASSWORD') ?: '' 44 | ext['signing.secretKeyRingFile'] = System.getenv('SIGNING_SECRET_KEY_RING_FILE') ?: '' 45 | ext['ossrhUsername'] = System.getenv('OSSRH_USERNAME') ?: '' 46 | ext['ossrhPassword'] = System.getenv('OSSRH_PASSWORD') ?: '' 47 | // Override with local.properties if available 48 | File secretPropsFile = project.rootProject.file('local.properties') 49 | if (secretPropsFile.exists()) { 50 | Properties p = new Properties() 51 | p.load(new FileInputStream(secretPropsFile)) 52 | p.each { name, value -> 53 | ext[name] = value 54 | } 55 | } 56 | 57 | // If the key content is in an environmental var, write it to "tmp/key.pgp" and update 58 | // ext['signing.secretKeyRingFile'] to point to it 59 | def pgpKeyContent = System.getenv('PGP_KEY_CONTENTS') 60 | if (pgpKeyContent != null) { 61 | def tmpDir = new File("$rootProject.rootDir/tmp") 62 | mkdir tmpDir 63 | def keyFile = new File("$tmpDir/key.pgp") 64 | keyFile.createNewFile() 65 | def os = keyFile.newDataOutputStream() 66 | os.write(pgpKeyContent.decodeBase64()) 67 | os.close() 68 | pgpKeyContent = '' 69 | 70 | ext['signing.secretKeyRingFile'] = keyFile.absolutePath 71 | } 72 | 73 | nexusPublishing { 74 | repositories { 75 | sonatype { 76 | stagingProfileId = 'e1e4b9ea52730' 77 | nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) 78 | snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) 79 | username = ossrhUsername 80 | password = ossrhPassword 81 | version = sdkVersion 82 | } 83 | } 84 | } 85 | 86 | gradle.projectsEvaluated { 87 | 88 | task aggregatedJavadoc(type: Javadoc, description: 'Generate javadocs from "common" and "txsdk" modules', 89 | group: JavaBasePlugin.DOCUMENTATION_GROUP) { 90 | options.encoding 'utf-8' 91 | options { 92 | addStringOption 'docencoding', 'utf-8' 93 | addStringOption 'charset', 'utf-8' 94 | addStringOption 'source', '8' 95 | addBooleanOption('Xdoclint:none', true) 96 | addStringOption 'overview', 'doc/readme.html' 97 | links 'https://d.android.com/reference' 98 | linksOffline 'https://d.android.com/reference/', 'https://d.android.com/reference/androidx/' 99 | } 100 | title = "Transifex Native SDK $sdkVersion" 101 | 102 | destinationDir project.file("$project.buildDir/docs/javadoc") 103 | 104 | Set projectSet = subprojects.findAll{ subproject -> 105 | subproject.name == 'txsdk' || subproject.name == 'common' 106 | } 107 | 108 | // Merge the properties of the androidJavadoc tasks of the subprojects 109 | source = projectSet.androidJavadoc.source 110 | classpath = project.files(projectSet.androidJavadoc.classpath) 111 | excludes = projectSet.androidJavadoc.excludes.flatten().unique() 112 | includes = projectSet.androidJavadoc.includes.flatten().unique() 113 | } 114 | } 115 | 116 | tasks.register('clean', Delete) { 117 | delete rootProject.buildDir 118 | delete "$rootProject.rootDir/tmp" 119 | } 120 | 121 | tasks.register('cleanTmp', Delete) { 122 | delete "$rootProject.rootDir/tmp" 123 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/clitool/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /TransifexNativeSDK/clitool/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | jar { 6 | manifest { 7 | attributes( 8 | 'Main-Class': 'com.transifex.clitool.MainClass', 9 | "Implementation-Title": "Transifex command-line tool for Android", 10 | "Implementation-Version": rootProject.ext.cliVersion 11 | ) 12 | } 13 | 14 | archivesBaseName = "transifex" 15 | 16 | // Add dependencies in the final jar file to create a fat jar 17 | // https://discuss.gradle.org/t/how-to-include-dependencies-in-jar/19571/18 18 | // exclude files: 19 | // https://kennethjorgensen.com/blog/2014/fat-jars-with-excluded-dependencies-in-gradle/ 20 | from { 21 | configurations.runtimeClasspath.filter { it.exists() }.collect { it.isDirectory() ? it : zipTree(it) } 22 | } { 23 | exclude "META-INF/versions/" 24 | exclude "META-INF/LICENSE.txt" 25 | } 26 | preserveFileTimestamps = false 27 | reproducibleFileOrder = true 28 | } 29 | 30 | java { 31 | sourceCompatibility = JavaVersion.VERSION_1_8 32 | targetCompatibility = JavaVersion.VERSION_1_8 33 | } 34 | 35 | // Read the source code using UTF-8 36 | // https://gist.github.com/rponte/d660919434d094bbd35a1aabf7ef1bf0 37 | compileJava.options.encoding = "UTF-8" 38 | compileTestJava.options.encoding = "UTF-8" 39 | javadoc.options.encoding = 'UTF-8' 40 | 41 | tasks.withType(Test).configureEach { 42 | // Use UTF-8 in the JVM that executes the test gradle tasks 43 | systemProperty "file.encoding", "UTF-8" 44 | } 45 | 46 | dependencies { 47 | compileOnly "androidx.annotation:annotation:$versions.androidXAnnotation" 48 | implementation 'org.jdom:jdom2:2.0.6.1' 49 | implementation 'info.picocli:picocli:4.7.5' 50 | implementation project(':common') 51 | 52 | testCompileOnly "androidx.annotation:annotation:$versions.androidXAnnotation" 53 | testImplementation "junit:junit:$versions.junit" 54 | testImplementation "com.google.truth:truth:$versions.truth" 55 | testImplementation "com.squareup.okhttp3:mockwebserver:$versions.okhttp3Mockwebserver" 56 | testImplementation "com.google.code.gson:gson:$versions.gson" 57 | testImplementation testFixtures(project(':common')) 58 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/clitool/testFiles/strings-test-at.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | using it here @ or escaped \@ is ok 4 | @resource this should be ignored 5 | \@ this is ok 6 | @ this is ok 7 | -------------------------------------------------------------------------------- /TransifexNativeSDK/clitool/testFiles/strings-test-backslash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | this \s should be ignored 4 | an escaped backslash is here \\ is and here \\ 5 | the single backslash at the end is preserved \ 6 | -------------------------------------------------------------------------------- /TransifexNativeSDK/clitool/testFiles/strings-test-htmlentities.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | these html entities & < > should be left as is 4 | -------------------------------------------------------------------------------- /TransifexNativeSDK/clitool/testFiles/strings-test-insidedoublequotes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | "multiple spaces are allowed here" but not here 4 | "single quotes ' ''" are allowed here but not here'' 5 | HTML entity quotes behave like normal " "quotes 6 | multiple new lines" 7 | 8 | " and "new lines 9 | 10 | with spaces" are allowed when in double quotes 11 | -------------------------------------------------------------------------------- /TransifexNativeSDK/clitool/testFiles/strings-test-new-line-space-tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | multiple spaces, 4 | 5 | a tab and new lines should be collapsed into a single space 6 | 7 | -------------------------------------------------------------------------------- /TransifexNativeSDK/clitool/testFiles/strings-test-new-line.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | a\n \\n \\\n \\\\nb 4 | actual new line 5 | should be replaced by space 6 | multiple actual new lines 7 | 8 | 9 | should be replaced by a single space 10 | -------------------------------------------------------------------------------- /TransifexNativeSDK/clitool/testFiles/strings-test-quote.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | a"b\"c\\"d 4 | a'b\'c\\'d 5 | This is a bold statement 6 | "\" 7 | -------------------------------------------------------------------------------- /TransifexNativeSDK/clitool/testFiles/strings-test-space.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | multiple spaces should be collapsed to a single space 4 | spaces at the beginning and some trailing ones 5 | -------------------------------------------------------------------------------- /TransifexNativeSDK/clitool/testFiles/strings-test-tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | a\tb\\tc 4 | actual tab should be replaced by space 5 | multiple escaped tabs\t\t\tare preserved as tabs 6 | multiple real tabs are collapsed to a single space 7 | -------------------------------------------------------------------------------- /TransifexNativeSDK/clitool/testFiles/strings-test-unicode.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ελληνικά! 4 | unicode heart: ❤ 5 | escaped heart: \u2764 6 | html decimal escaped heart: ❤ 7 | html hexadecimal escaped heart: ❤ 8 | long unicode emoji: 🧑‍🤝‍🧑 9 | -------------------------------------------------------------------------------- /TransifexNativeSDK/common/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /TransifexNativeSDK/common/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'java-test-fixtures' 4 | } 5 | 6 | java { 7 | sourceCompatibility = JavaVersion.VERSION_1_8 8 | targetCompatibility = JavaVersion.VERSION_1_8 9 | } 10 | 11 | compileJava.options.encoding = "UTF-8" 12 | compileTestJava.options.encoding = "UTF-8" 13 | compileTestFixturesJava.options.encoding = "UTF-8" 14 | javadoc.options.encoding = 'UTF-8' 15 | 16 | tasks.withType(Test).configureEach { 17 | systemProperty "file.encoding", "UTF-8" 18 | } 19 | 20 | // Disable publishing of test fixtures variants 21 | // https://docs.gradle.org/current/userguide/java_testing.html#sec:java_test_fixtures 22 | components.java.withVariantsFromConfiguration(configurations.testFixturesApiElements) { skip() } 23 | components.java.withVariantsFromConfiguration(configurations.testFixturesRuntimeElements) { skip() } 24 | 25 | dependencies { 26 | compileOnly "androidx.annotation:annotation:$versions.androidXAnnotation" 27 | implementation "com.google.code.gson:gson:$versions.gson" 28 | implementation 'net.moznion:uribuilder-tiny:2.7.1' 29 | 30 | testCompileOnly "androidx.annotation:annotation:$versions.androidXAnnotation" 31 | testImplementation "junit:junit:$versions.junit" 32 | testImplementation "com.google.truth:truth:$versions.truth" 33 | testImplementation "com.squareup.okhttp3:mockwebserver:$versions.okhttp3Mockwebserver" 34 | testImplementation 'commons-io:commons-io:2.11.0' 35 | 36 | testFixturesCompileOnly "androidx.annotation:annotation:$versions.androidXAnnotation" 37 | testFixturesCompileOnly "com.squareup.okhttp3:mockwebserver:$versions.okhttp3Mockwebserver" 38 | testFixturesImplementation "com.google.code.gson:gson:$versions.gson" 39 | } 40 | 41 | // Generate a BuildProperties Java class 42 | tasks.register('generateJava') { 43 | ext.outputDir = "$buildDir/generated/java" 44 | doLast { 45 | mkdir "$outputDir/com/transifex/common" 46 | file("$outputDir/com/transifex/common/BuildProperties.java").text = 47 | """|package com.transifex.common; 48 | |public class BuildProperties { 49 | | public static String getSDKVersion() { return "${rootProject.ext.sdkVersion}"; } 50 | | public static String getCLIVersion() { return "${rootProject.ext.cliVersion}"; } 51 | |}""".stripMargin() 52 | } 53 | } 54 | compileJava.dependsOn generateJava 55 | sourceSets.main.java.srcDir generateJava.outputDir 56 | 57 | apply from: '../publish-helper.gradle' -------------------------------------------------------------------------------- /TransifexNativeSDK/common/src/main/java/com/transifex/common/TranslationsDownloader.java: -------------------------------------------------------------------------------- 1 | package com.transifex.common; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.nio.channels.Channels; 9 | import java.nio.channels.FileChannel; 10 | import java.nio.channels.ReadableByteChannel; 11 | import java.util.HashMap; 12 | import java.util.Set; 13 | import java.util.logging.Level; 14 | import java.util.logging.Logger; 15 | 16 | import androidx.annotation.NonNull; 17 | import androidx.annotation.Nullable; 18 | 19 | /** 20 | * A class that makes use of {@link CDSHandler} to fetch translations from the CDS and save them 21 | * to files. 22 | */ 23 | public class TranslationsDownloader { 24 | 25 | private static final String TAG = CDSHandler.class.getSimpleName(); 26 | private static final Logger LOGGER = Logger.getLogger(TAG); 27 | 28 | private final CDSHandler mCDSHandler; 29 | 30 | public TranslationsDownloader(@NonNull CDSHandler cdsHandler) { 31 | mCDSHandler = cdsHandler; 32 | } 33 | 34 | /** 35 | * An {@link CDSHandler.FetchCallback} implementation that writes the provided input streams 36 | * to files. 37 | *

38 | * Each downloaded file is added on the filesMap using the respective locale code 39 | * as key. 40 | */ 41 | private static class DownloadTranslationsCallback implements CDSHandler.FetchCallback { 42 | 43 | File directory; 44 | String filename; 45 | 46 | HashMap filesMap = new HashMap<>(0); 47 | 48 | public DownloadTranslationsCallback(@NonNull File directory, @NonNull String filename) { 49 | this.directory = directory; 50 | this.filename = filename; 51 | } 52 | 53 | @Override 54 | public void onFetchingTranslations(@NonNull String[] localeCodes) { 55 | filesMap = new HashMap<>(localeCodes.length); 56 | } 57 | 58 | @Override 59 | public void onTranslationFetched(@Nullable InputStream inputStream, @NonNull String localeCode, @Nullable Exception exception) { 60 | if (inputStream == null) { 61 | return; 62 | } 63 | 64 | File localeDir = new File(directory.getAbsolutePath() + File.separator + localeCode); 65 | localeDir.mkdirs(); 66 | 67 | File localeFile = new File(localeDir.getAbsolutePath() + File.separator + filename); 68 | try { 69 | FileOutputStream fileOutputStream = new FileOutputStream(localeFile, false); 70 | 71 | FileChannel fileChannel = fileOutputStream.getChannel(); 72 | ReadableByteChannel readableByteChannel = Channels.newChannel(inputStream); 73 | fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE); 74 | readableByteChannel.close(); 75 | fileChannel.close(); 76 | 77 | filesMap.put(localeCode, localeFile); 78 | } catch (FileNotFoundException e) { 79 | LOGGER.log(Level.SEVERE, "Error writing file " + localeFile.getAbsolutePath()); 80 | } catch (IOException e) { 81 | LOGGER.log(Level.SEVERE, "IOException when reading response for locale " + localeCode + " : " + e); 82 | } 83 | } 84 | 85 | @Override 86 | public void onFailure(@NonNull Exception exception) {} 87 | } 88 | 89 | /** 90 | * Fetches translations from CDS and saves them to files. 91 | *

92 | * For each locale, a subdirectory under the provided directory is created and a file containing 93 | * the translations is created. Note that the provided directory should already exist. If a 94 | * translation file already exists, it's overwritten. 95 | * 96 | * @param localeCode An optional locale to fetch translations from; if set to null, 97 | * it will fetch translations for the locale codes configured in the 98 | * {@link CDSHandler} instance provided in the constructor. 99 | * @param tags An optional set of tags. If defined, only strings that have all of the given tags 100 | * will be fetched. 101 | * @param directory The directory on which to save the translations. The directory should 102 | * already exist. 103 | * @param filename The name of the translation file for a locale. 104 | * 105 | * @return A key-value map where each locale code points to the downloaded file containing the 106 | * translations. If an error occurs, some or all locale codes will be missing from the map. 107 | */ 108 | @NonNull 109 | public HashMap downloadTranslations(@Nullable String localeCode, 110 | @Nullable Set tags, 111 | @NonNull File directory, 112 | @NonNull String filename) { 113 | if (!directory.isDirectory()) { 114 | LOGGER.log(Level.SEVERE, "The provided directory does not exist: " + directory.getAbsolutePath()); 115 | return new HashMap<>(0); 116 | } 117 | if (filename == null || filename.isEmpty()) { 118 | LOGGER.log(Level.SEVERE, "The provided filename is not correct: " + filename); 119 | return new HashMap<>(0); 120 | } 121 | 122 | DownloadTranslationsCallback callback = new DownloadTranslationsCallback(directory, filename); 123 | mCDSHandler.fetchTranslations(localeCode, tags, callback); 124 | 125 | return callback.filesMap; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /TransifexNativeSDK/common/src/main/java/com/transifex/common/Utils.java: -------------------------------------------------------------------------------- 1 | package com.transifex.common; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.io.UnsupportedEncodingException; 9 | import java.net.URLEncoder; 10 | 11 | import androidx.annotation.NonNull; 12 | 13 | public class Utils { 14 | 15 | /** 16 | * Reads an input stream to a string. 17 | */ 18 | public @NonNull 19 | static String readInputStream(@NonNull InputStream inputStream) throws IOException { 20 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); 21 | StringBuilder result = new StringBuilder(); 22 | String line; 23 | while ((line = reader.readLine()) != null) { 24 | result.append(line); 25 | } 26 | 27 | reader.close(); 28 | 29 | return result.toString(); 30 | } 31 | 32 | 33 | /** 34 | * Deletes a directory including its contents 35 | * 36 | * @return true if and only if the file or directory is successfully deleted; 37 | * false otherwise 38 | */ 39 | public static boolean deleteDirectory(File directoryToBeDeleted) { 40 | File[] allContents = directoryToBeDeleted.listFiles(); 41 | if (allContents != null) { 42 | for (File file : allContents) { 43 | deleteDirectory(file); 44 | } 45 | } 46 | return directoryToBeDeleted.delete(); 47 | } 48 | 49 | /** 50 | * Deletes the directory's contents 51 | * 52 | * @return true if it's a directory and it's content is successfully deleted or 53 | * it's already empty; false otherwise 54 | */ 55 | public static boolean deleteDirectoryContents(File directoryToBeDeleted) { 56 | File[] allContents = directoryToBeDeleted.listFiles(); 57 | boolean success = false; 58 | if (allContents != null) { 59 | success = true; 60 | for (File file : allContents) { 61 | success &= deleteDirectory(file); 62 | } 63 | } 64 | return success; 65 | } 66 | 67 | /** 68 | * URL encodes the provided string. 69 | */ 70 | public static String urlEncode(String string) throws UnsupportedEncodingException { 71 | // Fixes URLEncoder's escaping of " " with "+" so that it works with URLs 72 | return URLEncoder.encode(string, "UTF-8").replace("+", "%20"); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /TransifexNativeSDK/common/src/test/java/com/transifex/common/PluralsTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.common; 2 | 3 | import org.junit.Test; 4 | import org.junit.function.ThrowingRunnable; 5 | 6 | import static com.google.common.truth.Truth.assertThat; 7 | import static org.junit.Assert.assertThrows; 8 | 9 | public class PluralsTest { 10 | 11 | @Test 12 | public void testFromICUString_normal() { 13 | String icuString = "{???, plural, zero {task0} one {task} two {task2} few {tasks} many {tasksss} other {tasks}}"; 14 | Plurals plurals = Plurals.fromICUString(icuString); 15 | 16 | assertThat(plurals).isNotNull(); 17 | assertThat(plurals.zero).isEqualTo("task0"); 18 | assertThat(plurals.one).isEqualTo("task"); 19 | assertThat(plurals.two).isEqualTo("task2"); 20 | assertThat(plurals.few).isEqualTo("tasks"); 21 | assertThat(plurals.many).isEqualTo("tasksss"); 22 | assertThat(plurals.other).isEqualTo("tasks"); 23 | } 24 | 25 | @Test 26 | public void testFromICUString_oneIncorrectPluralStyle_parseRestOfThem() { 27 | String icuString = "{???, plural, WRONG {task} other {tasks}}"; 28 | Plurals plurals = Plurals.fromICUString(icuString); 29 | 30 | assertThat(plurals).isNotNull(); 31 | assertThat(plurals.zero).isNull(); 32 | assertThat(plurals.one).isNull(); 33 | assertThat(plurals.two).isNull(); 34 | assertThat(plurals.few).isNull(); 35 | assertThat(plurals.many).isNull(); 36 | assertThat(plurals.other).isEqualTo("tasks"); 37 | } 38 | 39 | @Test 40 | public void testFromICUString_emptyString_returnNull() { 41 | String icuString = ""; 42 | Plurals plurals = Plurals.fromICUString(icuString); 43 | 44 | assertThat(plurals).isNull(); 45 | } 46 | 47 | @Test 48 | public void testBuilder_normal() { 49 | Plurals.Builder sb = new Plurals.Builder(); 50 | sb.setZero("none") 51 | .setOne("just one") 52 | .setTwo("just two") 53 | .setFew("a few") 54 | .setMany("many") 55 | .setOther("other!"); 56 | Plurals plurals = sb.buildString(); 57 | 58 | assertThat(plurals.zero).isEqualTo("none"); 59 | assertThat(plurals.one).isEqualTo("just one"); 60 | assertThat(plurals.two).isEqualTo("just two"); 61 | assertThat(plurals.few).isEqualTo("a few"); 62 | assertThat(plurals.many).isEqualTo("many"); 63 | assertThat(plurals.other).isEqualTo("other!"); 64 | } 65 | 66 | @Test 67 | public void testBuilder_otherNotSpecified_throwException() { 68 | Plurals.Builder sb = new Plurals.Builder(); 69 | 70 | assertThrows(Plurals.InvalidPluralsConfiguration.class, new ThrowingRunnable() { 71 | @Override 72 | public void run() throws Throwable { 73 | sb.buildString(); 74 | } 75 | }); 76 | } 77 | 78 | @Test 79 | public void testBuilderSetPlural_normal() { 80 | Plurals.Builder sb = new Plurals.Builder(); 81 | sb.setPlural(Plurals.PluralType.OTHER, "other!"); 82 | Plurals plurals = sb.buildString(); 83 | 84 | assertThat(plurals.zero).isNull(); 85 | assertThat(plurals.one).isNull(); 86 | assertThat(plurals.two).isNull(); 87 | assertThat(plurals.few).isNull(); 88 | assertThat(plurals.many).isNull(); 89 | assertThat(plurals.other).isEqualTo("other!"); 90 | } 91 | 92 | @Test 93 | public void testBuilderSetPlural_nonSupportedPluralType_throwException() { 94 | Plurals.Builder sb = new Plurals.Builder(); 95 | 96 | assertThrows(Plurals.NonSupportedPluralTypeException.class, new ThrowingRunnable() { 97 | @Override 98 | public void run() throws Throwable { 99 | sb.setPlural("Invalid plural type", "test"); 100 | } 101 | }); 102 | } 103 | 104 | @Test 105 | public void testToICUString_normal() { 106 | Plurals.Builder sb = new Plurals.Builder(); 107 | sb.setZero("none") 108 | .setOne("just one") 109 | .setTwo("just two") 110 | .setFew("a few") 111 | .setMany("many") 112 | .setOther("other!"); 113 | Plurals plurals = sb.buildString(); 114 | 115 | String icuString = plurals.toICUString(); 116 | 117 | assertThat(icuString).isEqualTo("{cnt, plural, zero {none} one {just one} two {just two} few {a few} many {many} other {other!}}"); 118 | } 119 | 120 | @Test 121 | public void testGetPlural_normal() { 122 | Plurals.Builder sb = new Plurals.Builder(); 123 | sb.setZero("none") 124 | .setOne("just one") 125 | .setTwo("just two") 126 | .setFew("a few") 127 | .setMany("many") 128 | .setOther("other!"); 129 | Plurals plurals = sb.buildString(); 130 | 131 | assertThat(plurals.getPlural(Plurals.PluralType.ZERO)).isEqualTo("none"); 132 | assertThat(plurals.getPlural(Plurals.PluralType.ONE)).isEqualTo("just one"); 133 | assertThat(plurals.getPlural(Plurals.PluralType.TWO)).isEqualTo("just two"); 134 | assertThat(plurals.getPlural(Plurals.PluralType.FEW)).isEqualTo("a few"); 135 | assertThat(plurals.getPlural(Plurals.PluralType.MANY)).isEqualTo("many"); 136 | assertThat(plurals.getPlural(Plurals.PluralType.OTHER)).isEqualTo("other!"); 137 | } 138 | 139 | @Test 140 | public void testGetPlural_nonSupportedPluralType_throwException() { 141 | Plurals.Builder sb = new Plurals.Builder(); 142 | sb.setZero("none") 143 | .setOne("just one") 144 | .setTwo("just two") 145 | .setFew("a few") 146 | .setMany("many") 147 | .setOther("other!"); 148 | Plurals plurals = sb.buildString(); 149 | 150 | assertThrows(Plurals.NonSupportedPluralTypeException.class, new ThrowingRunnable() { 151 | @Override 152 | public void run() throws Throwable { 153 | plurals.getPlural("Invalid plural type"); 154 | } 155 | }); 156 | 157 | } 158 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/common/src/testFixtures/java/com/transifex/common/StringTestData.java: -------------------------------------------------------------------------------- 1 | package com.transifex.common; 2 | 3 | import java.util.HashMap; 4 | 5 | public class StringTestData { 6 | 7 | public static LocaleData.LocaleStrings getElLocaleStrings() { 8 | HashMap map = new HashMap<>(); 9 | map.put("test_key", new LocaleData.StringInfo("Καλημέρα")); 10 | map.put("test_key3", new LocaleData.StringInfo("")); 11 | return new LocaleData.LocaleStrings(map); 12 | } 13 | 14 | public static LocaleData.LocaleStrings getEsLocaleStrings() { 15 | HashMap map = new HashMap<>(); 16 | map.put("test_key", new LocaleData.StringInfo("Buenos días")); 17 | map.put("test_key3", new LocaleData.StringInfo("")); 18 | return new LocaleData.LocaleStrings(map); 19 | } 20 | 21 | public static LocaleData.TranslationMap getElEsTranslationMap() { 22 | LocaleData.TranslationMap translationMap = new LocaleData.TranslationMap(2); 23 | translationMap.put("el", getElLocaleStrings()); 24 | translationMap.put("es", getEsLocaleStrings()); 25 | return translationMap; 26 | } 27 | 28 | public static LocaleData.TranslationMap getElTranslationMap() { 29 | LocaleData.TranslationMap translationMap = new LocaleData.TranslationMap(1); 30 | translationMap.put("el", getElLocaleStrings()); 31 | return translationMap; 32 | } 33 | 34 | public static LocaleData.TranslationMap getEsTranslationMap() { 35 | LocaleData.TranslationMap translationMap = new LocaleData.TranslationMap(1); 36 | translationMap.put("es", getElLocaleStrings()); 37 | return translationMap; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /TransifexNativeSDK/common/src/testFixtures/java/com/transifex/common/TempDirHelper.java: -------------------------------------------------------------------------------- 1 | package com.transifex.common; 2 | 3 | import java.io.File; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | /** 8 | * A helper method that contains the boilerplate code for making sure that a temporary 9 | * directory does not exist when performing tests. By default the directory is 10 | * "build/unitTestTempDir", but can optionally specify a different directory. 11 | *

12 | * The directory is deleted, if it exists, during setup. It is also deleted during tear down. 13 | */ 14 | public class TempDirHelper { 15 | 16 | private final File tempDir; 17 | 18 | public TempDirHelper() { 19 | tempDir = new File("build" + File.separator + "unitTestTempDir"); 20 | } 21 | 22 | public TempDirHelper(@NonNull File dir) { 23 | tempDir = dir; 24 | } 25 | 26 | public void setUp() { 27 | if (tempDir.exists()) { 28 | Utils.deleteDirectory(tempDir); 29 | } 30 | } 31 | 32 | public void tearDown() { 33 | if (tempDir.exists()) { 34 | boolean deleted = Utils.deleteDirectory(tempDir); 35 | if (!deleted) { 36 | System.out.println("Could not delete tmp dir after test. Next test may fail."); 37 | } 38 | } 39 | } 40 | 41 | public File getFile() { 42 | return tempDir; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /TransifexNativeSDK/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | 21 | # https://developer.android.com/build/releases/past-releases/agp-8-0-0-release-notes#default-changes 22 | android.defaults.buildfeatures.buildconfig=true 23 | android.nonTransitiveRClass=false 24 | android.nonFinalResIds=false -------------------------------------------------------------------------------- /TransifexNativeSDK/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /TransifexNativeSDK/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Sep 20 14:45:43 EEST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /TransifexNativeSDK/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /TransifexNativeSDK/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /TransifexNativeSDK/publish-helper.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | apply plugin: 'signing' 3 | 4 | tasks.register('androidJavadoc', Javadoc) { 5 | if (plugins.hasPlugin('android-library')) { 6 | source = android.sourceSets.main.java.srcDirs 7 | classpath += project.files(android.getBootClasspath()) 8 | android.libraryVariants.all { variant -> 9 | if (variant.name == 'release') { 10 | owner.classpath += variant.javaCompileProvider.get().classpath 11 | } 12 | } 13 | } else { 14 | source = sourceSets.main.allJava 15 | classpath += configurations.runtimeClasspath 16 | classpath += configurations.compileClasspath 17 | } 18 | exclude '**/R.html', '**/R.*.html', '**/index.html', '**/*.kt' 19 | options.encoding 'utf-8' 20 | options { 21 | addStringOption 'docencoding', 'utf-8' 22 | addStringOption 'charset', 'utf-8' 23 | addStringOption 'source', '8' 24 | addBooleanOption('Xdoclint:none', true) 25 | links 'https://d.android.com/reference' 26 | linksOffline 'https://d.android.com/reference/', 'https://d.android.com/reference/androidx/' 27 | } 28 | } 29 | 30 | tasks.register('androidJavadocJar', Jar) { 31 | dependsOn androidJavadoc 32 | 33 | archiveClassifier.set('javadoc') 34 | from androidJavadoc.destinationDir 35 | preserveFileTimestamps = false 36 | reproducibleFileOrder = true 37 | } 38 | 39 | tasks.register('javaSourcesJar', Jar) { 40 | archiveClassifier.set('sources') 41 | if (plugins.hasPlugin('android-library')) { 42 | from android.sourceSets.main.java.srcDirs 43 | } else { 44 | from sourceSets.main.allSource 45 | } 46 | preserveFileTimestamps = false 47 | reproducibleFileOrder = true 48 | } 49 | 50 | afterEvaluate { 51 | publishing { 52 | publications { 53 | // Creates a Maven publication called 'release'. 54 | release(MavenPublication) { 55 | if (plugins.hasPlugin('android-library')) { 56 | from components.release 57 | 58 | artifact androidJavadocJar 59 | // artifact javaSourcesJar is removed. It's created by AGP in the android 60 | // publishing block. 61 | } 62 | else if (plugins.hasPlugin('java')) { 63 | from components.java 64 | jar.preserveFileTimestamps = false 65 | jar.reproducibleFileOrder = true 66 | 67 | artifact androidJavadocJar 68 | artifact javaSourcesJar 69 | } 70 | 71 | 72 | groupId rootProject.ext.pomGroupID 73 | version rootProject.ext.sdkVersion 74 | pom { 75 | name = artifactId 76 | description = 'Transifex Native library for Android' 77 | url = 'https://github.com/transifex/transifex-java' 78 | licenses { 79 | license { 80 | name = 'The Apache License, Version 2.0' 81 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 82 | } 83 | } 84 | scm { 85 | connection = 'scm:git:https://github.com/transifex/transifex-java.git' 86 | url = 'https://github.com/transifex/transifex-java' 87 | } 88 | developers { 89 | developer { 90 | id = 'petrakeas' 91 | name = 'Petros Douvantzis' 92 | email = 'petrakeas@gmail.com' 93 | } 94 | } 95 | } 96 | } 97 | } 98 | // No repositories are configured here, since"gradle-nexus.publish-plugin" will create its 99 | // own using the configuration in the project's gradle file 100 | } 101 | } 102 | 103 | signing { 104 | sign publishing.publications 105 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | 16 | include ':common' 17 | include ':clitool' 18 | include ':txsdk' 19 | include ':app' -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | namespace = 'com.transifex.txnative' 8 | 9 | defaultConfig { 10 | minSdkVersion 18 11 | compileSdk 33 12 | targetSdkVersion 33 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles "consumer-rules.pro" 16 | 17 | // gradle.startParameter.taskNames.each { 18 | // if (it.contains("AndroidTest")) { 19 | // multiDexEnabled true 20 | // } 21 | // } 22 | } 23 | 24 | testOptions { 25 | unitTests.includeAndroidResources = true 26 | } 27 | 28 | buildTypes { 29 | release { 30 | minifyEnabled false 31 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 32 | } 33 | } 34 | 35 | compileOptions { 36 | sourceCompatibility JavaVersion.VERSION_1_8 37 | targetCompatibility JavaVersion.VERSION_1_8 38 | } 39 | 40 | kotlinOptions { 41 | jvmTarget = '1.8' 42 | } 43 | 44 | // https://developer.android.com/studio/publish-library/configure-pub-variants 45 | publishing { 46 | singleVariant('release') { 47 | // Currently, Javadoc generation from AGP does not have the same quality as the one 48 | // generated by the script at publish-helper.gradle so it's commented out. 49 | // withJavadocJar() 50 | withSourcesJar() 51 | } 52 | } 53 | } 54 | 55 | tasks.withType(Test).configureEach { 56 | systemProperty "file.encoding", "UTF-8" 57 | } 58 | 59 | configurations.configureEach { 60 | resolutionStrategy { 61 | force "androidx.annotation:annotation:$versions.androidXAnnotation" 62 | } 63 | } 64 | 65 | dependencies { 66 | implementation fileTree(dir: "libs", include: ["*.jar"]) 67 | compileOnly "androidx.annotation:annotation:$versions.androidXAnnotation" 68 | implementation "androidx.appcompat:appcompat:$versions.appcompat" 69 | compileOnly "com.google.android.material:material:$versions.material" 70 | implementation("dev.b3nedikt.viewpump:viewpump:4.0.14") { 71 | exclude(group : "androidx.appcompat", module : "appcompat") 72 | } 73 | 74 | api project(':common') 75 | 76 | testCompileOnly "androidx.annotation:annotation:$versions.androidXAnnotation" 77 | testImplementation "junit:junit:$versions.junit" 78 | testImplementation "com.google.truth:truth:$versions.truth" 79 | testImplementation "androidx.test:core:$versions.androidXTestCore" 80 | testImplementation "org.robolectric:robolectric:$versions.roboelectric" 81 | testImplementation "com.squareup.okhttp3:mockwebserver:$versions.okhttp3Mockwebserver" 82 | testImplementation "org.mockito:mockito-core:$versions.mockitoCore" 83 | testImplementation testFixtures(project(':common')) 84 | 85 | androidTestCompileOnly "androidx.annotation:annotation:$versions.androidXAnnotation" 86 | androidTestImplementation "androidx.test.ext:junit:$versions.androidXJunit" 87 | androidTestImplementation "androidx.test.espresso:espresso-core:$versions.androidxEspressoCore" 88 | androidTestImplementation "com.google.truth:truth:$versions.truth" 89 | androidTestImplementation testFixtures(project(':common')) 90 | } 91 | 92 | apply from: '../publish-helper.gradle' 93 | 94 | afterEvaluate { 95 | publishing { 96 | publications { 97 | release(MavenPublication) { 98 | // Specify custom artifactId if needed, 99 | // otherwise it would use module's name by default. 100 | artifactId = "txsdk" 101 | } 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | # Keep LocaleData inner classes as is, so that Gson works as expected 2 | -keep class com.transifex.common.LocaleData { *; } 3 | -keep class com.transifex.common.LocaleData$* { *; } 4 | 5 | # Keep class names for better log ouput 6 | -keepnames class com.transifex.txnative.** 7 | -keepnames class com.transifex.common.** 8 | 9 | # Don't warn 10 | -dontwarn lombok.NonNull -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/androidTest/assets/test_fileInsteadLocaleDir/dummy_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/txsdk/src/androidTest/assets/test_fileInsteadLocaleDir/dummy_file -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/androidTest/assets/test_normal/el/strings.txt: -------------------------------------------------------------------------------- 1 | {"data":{"test_key":{"string":"Καλημέρα"},"another_key":{"string":"Καλό απόγευμα"},"key3":{"string":""}}} -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/androidTest/assets/test_normal/es/strings.txt: -------------------------------------------------------------------------------- 1 | {"data":{"test_key":{"string":"Buenos días"},"another_key":{"string":"Buenas tardes"},"key3":{"string":""}}} -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/androidTest/assets/test_oneLocaleFileIsMissing/el/strings.txt: -------------------------------------------------------------------------------- 1 | {"data":{"test_key":{"string":"Καλημέρα"},"another_key":{"string":"Καλό απόγευμα"},"key3":{"string":""}}} -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/androidTest/assets/test_oneLocaleFileIsMissing/es/wrong_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/txsdk/src/androidTest/assets/test_oneLocaleFileIsMissing/es/wrong_file -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/androidTest/assets/test_oneLocaleHasInvalidJson/el/strings.txt: -------------------------------------------------------------------------------- 1 | {"data":{"test_key":{"string":"Καλημέρα"},"another_key":{"string":"Καλό απόγευμα"},"key3":{"string":""}}} -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/androidTest/assets/test_oneLocaleHasInvalidJson/es/strings.txt: -------------------------------------------------------------------------------- 1 | not a JSON format -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/androidTest/assets/txnative/el/txstrings.json: -------------------------------------------------------------------------------- 1 | {"data":{"test_key":{"string":"Καλημέρα"},"another_key":{"string":"Καλό απόγευμα"},"key3":{"string":""}}} -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/androidTest/assets/txnative/es/txstrings.json: -------------------------------------------------------------------------------- 1 | {"data":{"test_key":{"string":"Buenos días"},"another_key":{"string":"Buenas tardes"},"key3":{"string":""}}} -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/androidTest/java/com/transifex/txnative/PluralsTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative; 2 | 3 | import com.transifex.common.Plurals; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | 8 | import androidx.test.ext.junit.runners.AndroidJUnit4; 9 | import androidx.test.filters.SmallTest; 10 | 11 | import static com.google.common.truth.Truth.assertThat; 12 | 13 | @RunWith(AndroidJUnit4.class) 14 | @SmallTest 15 | public class PluralsTest { 16 | 17 | // The following test is copied from the respective unit test. We just want to make sure the 18 | // internal regex patter compiles correctly on a device. 19 | 20 | @Test 21 | public void testFromICUString_oneIncorrectPluralStyle_parseRestOfThem() { 22 | String icuString = "{???, plural, WRONG {task} other {tasks}}"; 23 | Plurals plurals = Plurals.fromICUString(icuString); 24 | 25 | assertThat(plurals).isNotNull(); 26 | assertThat(plurals.zero).isNull(); 27 | assertThat(plurals.one).isNull(); 28 | assertThat(plurals.two).isNull(); 29 | assertThat(plurals.few).isNull(); 30 | assertThat(plurals.many).isNull(); 31 | assertThat(plurals.other).isEqualTo("tasks"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/androidTest/java/com/transifex/txnative/TranslationMapStorageAndroidTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | 6 | import com.transifex.common.LocaleData; 7 | 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | 12 | import androidx.test.ext.junit.runners.AndroidJUnit4; 13 | import androidx.test.filters.SmallTest; 14 | import androidx.test.platform.app.InstrumentationRegistry; 15 | 16 | import static com.google.common.truth.Truth.assertThat; 17 | 18 | @RunWith(AndroidJUnit4.class) 19 | @SmallTest 20 | public class TranslationMapStorageAndroidTest { 21 | 22 | // The tests rely on the following directories: 23 | // 24 | // androidTest/assets/test_normal 25 | // androidTest/assets/test_oneLocaleHasInvalidJson 26 | // androidTest/assets/test_oneLocaleFileIsMissing 27 | // androidTest/assets/test_fileInsteadLocaleDir 28 | // 29 | // Note that aapt2 will remove empty directories when packing the assets. So there is no reason 30 | // to test for an empty directory. 31 | 32 | Context appContext = null; 33 | AssetManager assetManager; 34 | 35 | @Before 36 | public void setUp() { 37 | appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 38 | assetManager = appContext.getAssets(); 39 | } 40 | 41 | @Test 42 | public void testFromAssetsDirectory_dirDoesNotExist_returnNullTranslationMap() { 43 | TranslationMapStorageAndroid reader = new TranslationMapStorageAndroid(assetManager, "strings.txt"); 44 | LocaleData.TranslationMap map = reader.fromAssetsDirectory("wrongDir"); 45 | 46 | assertThat(map).isNull(); 47 | } 48 | 49 | @Test 50 | public void testFromAssetsDirectory_haveFileWhereLocaleDirExpected_returnNullTranslationMap() { 51 | TranslationMapStorageAndroid reader = new TranslationMapStorageAndroid(assetManager, "strings.txt"); 52 | LocaleData.TranslationMap map = reader.fromAssetsDirectory("test_fileInsteadLocaleDir"); 53 | 54 | assertThat(map).isNull(); 55 | } 56 | 57 | @Test 58 | public void testFromAssetsDirectory_normal_returnNormalTranslationMap() { 59 | TranslationMapStorageAndroid reader = new TranslationMapStorageAndroid(assetManager, "strings.txt"); 60 | LocaleData.TranslationMap map = reader.fromAssetsDirectory("test_normal"); 61 | 62 | assertThat(map).isNotNull(); 63 | assertThat(map.getLocales()).containsExactly("el", "es"); 64 | 65 | LocaleData.LocaleStrings elStrings = map.get("el"); 66 | assertThat(elStrings).isNotNull(); 67 | assertThat(elStrings.get("test_key")).isEqualTo("Καλημέρα"); 68 | assertThat(elStrings.get("another_key")).isEqualTo("Καλό απόγευμα"); 69 | assertThat(elStrings.get("key3")).isEqualTo(""); 70 | 71 | LocaleData.LocaleStrings esStrings = map.get("es"); 72 | assertThat(esStrings).isNotNull(); 73 | assertThat(esStrings.get("test_key")).isEqualTo("Buenos días"); 74 | assertThat(esStrings.get("another_key")).isEqualTo("Buenas tardes"); 75 | assertThat(esStrings.get("key3")).isEqualTo(""); 76 | } 77 | 78 | @Test 79 | public void testFromAssetsDirectory_oneLocaleHasInvalidJson_returnTranslationMapWithTheRestLocales() { 80 | TranslationMapStorageAndroid reader = new TranslationMapStorageAndroid(assetManager, "strings.txt"); 81 | LocaleData.TranslationMap map = reader.fromAssetsDirectory("test_oneLocaleHasInvalidJson"); 82 | 83 | assertThat(map).isNotNull(); 84 | assertThat(map.getLocales()).containsExactly("el"); 85 | 86 | LocaleData.LocaleStrings elStrings = map.get("el"); 87 | assertThat(elStrings).isNotNull(); 88 | assertThat(elStrings.get("test_key")).isEqualTo("Καλημέρα"); 89 | assertThat(elStrings.get("another_key")).isEqualTo("Καλό απόγευμα"); 90 | assertThat(elStrings.get("key3")).isEqualTo(""); 91 | } 92 | 93 | @Test 94 | public void testFromAssetsDirector_oneLocaleHasEmptyDir_getTranslationMapWithTheRestLocales() { 95 | TranslationMapStorageAndroid reader = new TranslationMapStorageAndroid(assetManager, "strings.txt"); 96 | LocaleData.TranslationMap map = reader.fromAssetsDirectory("test_oneLocaleFileIsMissing"); 97 | 98 | assertThat(map).isNotNull(); 99 | assertThat(map.getLocales()).containsExactly("el"); 100 | 101 | LocaleData.LocaleStrings elStrings = map.get("el"); 102 | assertThat(elStrings).isNotNull(); 103 | assertThat(elStrings.get("test_key")).isEqualTo("Καλημέρα"); 104 | assertThat(elStrings.get("another_key")).isEqualTo("Καλό απόγευμα"); 105 | assertThat(elStrings.get("key3")).isEqualTo(""); 106 | } 107 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/androidTest/java/com/transifex/txnative/TranslationMapStorageTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative; 2 | 3 | import android.content.Context; 4 | 5 | import com.transifex.common.LocaleData; 6 | import com.transifex.common.StringTestData; 7 | import com.transifex.common.TempDirHelper; 8 | import com.transifex.common.TranslationMapStorage; 9 | 10 | import org.junit.After; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | 15 | import java.io.File; 16 | import java.util.HashMap; 17 | 18 | import androidx.test.ext.junit.runners.AndroidJUnit4; 19 | import androidx.test.filters.SmallTest; 20 | import androidx.test.platform.app.InstrumentationRegistry; 21 | 22 | import static com.google.common.truth.Truth.assertThat; 23 | 24 | @RunWith(AndroidJUnit4.class) 25 | @SmallTest 26 | public class TranslationMapStorageTest { 27 | 28 | // The following test is copied from the respective unit test. 29 | 30 | Context appContext = null; 31 | TempDirHelper tempDirHelper = null; 32 | 33 | @Before 34 | public void setUp() { 35 | appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 36 | File filesDir = appContext.getFilesDir(); 37 | File tempDir = new File(filesDir.getPath() + File.separator + "unitTestTempDir"); 38 | tempDirHelper = new TempDirHelper(tempDir); 39 | tempDirHelper.setUp(); 40 | } 41 | 42 | @After 43 | public void Teardown() { 44 | if (tempDirHelper != null) { 45 | tempDirHelper.tearDown(); 46 | } 47 | } 48 | 49 | @Test 50 | // Check that we can read what was written 51 | public void testToDiskFromDisk_normal() { 52 | boolean tempDirCreated = tempDirHelper.getFile().mkdirs(); 53 | assertThat(tempDirCreated).isTrue(); 54 | 55 | LocaleData.TranslationMap translationMap = StringTestData.getElEsTranslationMap(); 56 | TranslationMapStorage storage = new TranslationMapStorage("strings.txt"); 57 | HashMap files = storage.toDisk(translationMap, tempDirHelper.getFile()); 58 | 59 | assertThat(files).isNotNull(); 60 | assertThat(files.keySet()).containsExactly("el", "es"); 61 | 62 | LocaleData.TranslationMap map = storage.fromDisk(tempDirHelper.getFile()); 63 | assertThat(map).isNotNull(); 64 | assertThat(map).isEqualTo(translationMap); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/androidTest/java/com/transifex/txnative/cache/TxDiskTranslationsProviderAssetsTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | 6 | import com.transifex.common.LocaleData; 7 | import com.transifex.common.TranslationMapStorage; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | 13 | import androidx.test.ext.junit.runners.AndroidJUnit4; 14 | import androidx.test.filters.SmallTest; 15 | import androidx.test.platform.app.InstrumentationRegistry; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | 19 | @RunWith(AndroidJUnit4.class) 20 | @SmallTest 21 | public class TxDiskTranslationsProviderAssetsTest { 22 | 23 | // This test, in contrast to the unit test found in the "test" folder, tests the AssetManager 24 | // version of TxDiskTranslationsProvider. 25 | 26 | // The tests rely on the following directory: 27 | // 28 | // androidTest/assets/txnative 29 | // 30 | // Note that aapt2 will remove empty directories when packing the assets. So there is no reason 31 | // to test for an empty directory. 32 | 33 | Context appContext = null; 34 | AssetManager assetManager; 35 | 36 | @Before 37 | public void setUp() { 38 | appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 39 | assetManager = appContext.getAssets(); 40 | } 41 | 42 | @Test 43 | public void testGetTranslations_translationsInAssets() { 44 | TxDiskTranslationsProvider provider = new TxDiskTranslationsProvider(assetManager, TranslationMapStorage.DEFAULT_TRANSLATIONS_DIR_NAME); 45 | LocaleData.TranslationMap map = provider.getTranslations(); 46 | 47 | assertThat(map).isEqualTo(TxStandardCacheTest.getAssetsEquivalentTranslationMap()); 48 | } 49 | 50 | @Test 51 | public void testGetTranslations_dirDoesNotExist_returnNullTranslationMap() { 52 | TxDiskTranslationsProvider provider = new TxDiskTranslationsProvider(assetManager, "wrongDir"); 53 | 54 | assertThat(provider.getTranslations()).isNull(); 55 | } 56 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/CDSHandlerAndroid.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative; 2 | 3 | import android.util.Log; 4 | 5 | import com.transifex.common.CDSHandler; 6 | import com.transifex.common.LocaleData; 7 | 8 | import java.util.Set; 9 | import java.util.concurrent.Executor; 10 | import java.util.concurrent.Executors; 11 | import java.util.concurrent.RejectedExecutionException; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | import androidx.annotation.WorkerThread; 16 | 17 | /** 18 | * A class that extends {@link CDSHandler} by adding a method that can fetch translations 19 | * asynchronously. 20 | * 21 | * @see CDSHandler 22 | */ 23 | public class CDSHandlerAndroid extends CDSHandler { 24 | 25 | private static final String TAG = CDSHandler.class.getSimpleName(); 26 | 27 | private final Executor mExecutor; 28 | 29 | /** 30 | * A callback that provides the results of {@link #fetchTranslationsAsync(String, Set, FetchTranslationsCallback)} 31 | * when the operation is complete. 32 | */ 33 | interface FetchTranslationsCallback { 34 | 35 | /** 36 | * Called when the operation is complete. 37 | *

38 | * If the operation fails, the translationMap will be empty. 39 | * 40 | * @param translationMap A {@link com.transifex.common.LocaleData.TranslationMap TranslationMap} 41 | * holding the results. 42 | */ 43 | @WorkerThread 44 | void onComplete(@NonNull LocaleData.TranslationMap translationMap); 45 | } 46 | 47 | /** 48 | * Creates a CDSHandler instance. 49 | * 50 | * @param localeCodes An array of locale codes that can be downloaded from CDS. The source 51 | * locale can also be included. 52 | * @param token The API token to use for connecting to the CDS. 53 | * @param secret The API secret to use for connecting to the CDS. 54 | * @param csdHost The host of the Content Delivery Service. 55 | */ 56 | public CDSHandlerAndroid(@Nullable String[] localeCodes, @NonNull String token, @Nullable String secret, @NonNull String csdHost) { 57 | super(localeCodes, token, secret, csdHost); 58 | mExecutor = Executors.newSingleThreadExecutor(); 59 | } 60 | 61 | /** 62 | * Fetch translations from CDS. 63 | *

64 | * The method is asynchronous. The callback is called on a background thread. 65 | * 66 | * @param localeCode An optional locale to fetch translations from; if set to null, 67 | * it will fetch translations for the locale codes provided in the constructor. 68 | * @param tags An optional set of tags. If defined, only strings that have all of the given tags 69 | * will be fetched. 70 | * @param callback A callback function to call when the operation is complete. 71 | */ 72 | public void fetchTranslationsAsync(@Nullable final String localeCode, 73 | @Nullable final Set tags, 74 | @NonNull final FetchTranslationsCallback callback) { 75 | try { 76 | mExecutor.execute(new Runnable() { 77 | @Override 78 | public void run() { 79 | LocaleData.TranslationMap result = fetchTranslations(localeCode, tags); 80 | callback.onComplete(result); 81 | } 82 | }); 83 | } 84 | catch (RejectedExecutionException exception) { 85 | Log.e(TAG, "Could not execute background task: " + exception); 86 | callback.onComplete(new LocaleData.TranslationMap(0)); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/TranslationMapStorageAndroid.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative; 2 | 3 | import android.content.res.AssetManager; 4 | 5 | import com.transifex.common.LocaleData; 6 | import com.transifex.common.TranslationMapStorage; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.annotation.Nullable; 14 | 15 | /** 16 | * A class that extends {@link TranslationMapStorage} so that translations can be read from the 17 | * application's Assets folder. 18 | *

19 | * Translations can be bundled in your application's Assets folder, using the Transifex command-line 20 | * tool. 21 | * 22 | * @see TranslationMapStorage 23 | */ 24 | public class TranslationMapStorageAndroid extends TranslationMapStorage { 25 | 26 | public static final String TAG = TranslationMapStorageAndroid.class.getSimpleName(); 27 | 28 | private final AssetFileProvider assetFileProvider; 29 | 30 | /** 31 | * Creates a new instance. 32 | * 33 | * @param manager An instance of Android's {@link AssetManager}; 34 | * @param filename The name of a locale's translation file. 35 | * 36 | * @see TranslationMapStorage 37 | */ 38 | public TranslationMapStorageAndroid(@NonNull AssetManager manager, @NonNull String filename) { 39 | super(filename); 40 | assetFileProvider = new AssetFileProvider(manager); 41 | } 42 | 43 | /** 44 | * Loads a {@link LocaleData.TranslationMap} from an application's Assets folder under the 45 | * provided path. 46 | * 47 | * @param srcDirectoryPath The path to the directory containing translations in the expected 48 | * format. 49 | * 50 | * @return The translation map or null if the directory isn't found or it's empty. 51 | * If some locales fail to load, they won't be added in the returned map. 52 | */ 53 | public @Nullable LocaleData.TranslationMap fromAssetsDirectory(@NonNull String srcDirectoryPath) { 54 | return fromDisk(assetFileProvider, assetFileProvider.getFile(srcDirectoryPath)); 55 | } 56 | 57 | //region AssetFile 58 | 59 | /** 60 | * An implementation that uses Android's AssetManager. 61 | */ 62 | private static class AssetFile implements AbstractFile { 63 | 64 | private final AssetManager manager; 65 | private final String pathname; 66 | 67 | public AssetFile(@NonNull AssetManager manager, @NonNull String pathname) { 68 | this.manager = manager; 69 | // Remove trailing slash 70 | if (pathname.endsWith("/")) { 71 | pathname = pathname.substring(0, pathname.length() - 1); 72 | } 73 | this.pathname = pathname; 74 | } 75 | 76 | @Nullable 77 | @Override 78 | public String[] list() { 79 | try { 80 | // If the dir does not exist, it returns an empty list. The java.io.File#list() 81 | // returns null. 82 | return manager.list(pathname); 83 | } catch (IOException ignored) {} 84 | 85 | return null; 86 | } 87 | 88 | @NonNull 89 | @Override 90 | public InputStream open() throws IOException { 91 | return manager.open(pathname); 92 | } 93 | 94 | @NonNull 95 | @Override 96 | public String getPath() { 97 | return pathname; 98 | } 99 | 100 | @NonNull 101 | @Override 102 | public String getAbsolutePath() { 103 | // There is no absolute path 104 | return pathname; 105 | } 106 | 107 | @Override 108 | public boolean isDirectory() { 109 | // There is no way to check if it's a directory 110 | return true; 111 | } 112 | } 113 | 114 | /** 115 | * A provider that returns an {@link AssetFile}. 116 | */ 117 | private static class AssetFileProvider implements AbstractFileProvider { 118 | 119 | private final AssetManager manager; 120 | 121 | public AssetFileProvider(@NonNull AssetManager manager) { 122 | this.manager = manager; 123 | } 124 | 125 | @NonNull 126 | @Override 127 | public AssetFile getFile(@NonNull String pathname) { 128 | return new AssetFile(manager, pathname); 129 | } 130 | } 131 | 132 | //endregion 133 | } 134 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/TxInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.widget.TextView; 8 | import android.widget.Toolbar; 9 | 10 | import com.google.android.material.textfield.TextInputLayout; 11 | import com.transifex.txnative.transformers.SupportToolbarTransformer; 12 | import com.transifex.txnative.transformers.TextInputLayoutTransformer; 13 | import com.transifex.txnative.transformers.TextViewTransformer; 14 | import com.transifex.txnative.transformers.ToolbarTransformer; 15 | import com.transifex.txnative.transformers.ViewTransformer; 16 | 17 | import androidx.annotation.NonNull; 18 | import dev.b3nedikt.viewpump.InflateResult; 19 | import dev.b3nedikt.viewpump.Interceptor; 20 | 21 | /** 22 | * ViewPump interceptor that transforms inflated views using the appropriate 23 | * {@link ViewTransformer}. 24 | *

25 | * ViewTransforms should change the text elements of views by calling their respective methods. 26 | * Since we have wrapped the context and thus the resources, all string related calls will be 27 | * handled by {@link TxResources}. 28 | *

29 | */ 30 | class TxInterceptor implements Interceptor { 31 | 32 | public static final String TAG = TxInterceptor.class.getSimpleName(); 33 | 34 | private final ViewTransformer mViewTransformer; 35 | private final TextViewTransformer mTextViewTransformer; 36 | private ToolbarTransformer mToolbarTransformer; 37 | private final SupportToolbarTransformer mSupportToolbarTransformer; 38 | private final TextInputLayoutTransformer mTextInputLayoutTransformer; 39 | 40 | public TxInterceptor() { 41 | mViewTransformer = new ViewTransformer(); 42 | mTextViewTransformer = new TextViewTransformer(); 43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 44 | mToolbarTransformer = new ToolbarTransformer(); 45 | } 46 | mSupportToolbarTransformer = new SupportToolbarTransformer(); 47 | mTextInputLayoutTransformer = new TextInputLayoutTransformer(); 48 | } 49 | 50 | @NonNull 51 | @Override 52 | public InflateResult intercept(@NonNull Chain chain) { 53 | InflateResult result = chain.proceed(chain.request()); 54 | 55 | View view = result.getView(); 56 | if (view == null) { 57 | return result; 58 | } 59 | 60 | AttributeSet attrs = result.getAttrs(); 61 | Context context = result.getContext(); 62 | if (attrs != null) { 63 | if (view instanceof TextView) { 64 | mTextViewTransformer.transform(context, view, attrs); 65 | } 66 | else if (Utils.isAppcompatPresent() && view instanceof androidx.appcompat.widget.Toolbar) { 67 | mSupportToolbarTransformer.transform(context, view, attrs); 68 | } 69 | else if (Utils.isMaterialComponentsPresent() && view instanceof TextInputLayout) { 70 | mTextInputLayoutTransformer.transform(context, view, attrs); 71 | } 72 | else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && view instanceof Toolbar) { 73 | mToolbarTransformer.transform(context, view, attrs); 74 | } 75 | else { 76 | mViewTransformer.transform(context, view, attrs); 77 | } 78 | } 79 | 80 | return result; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/activity/TxBaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.activity; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.res.Resources; 6 | 7 | import com.transifex.txnative.TxNative; 8 | 9 | /** 10 | * A base activity that implements context wrapping so that the TxNative functionality is enabled. 11 | *

12 | * Make sure your activities extend this class or have the same implementation. 13 | *

14 | * If your app uses Appcompat don't use this class. Instead use 15 | * {@link TxBaseAppCompatActivity}. 16 | */ 17 | @Deprecated 18 | class TxBaseActivity extends Activity { 19 | 20 | 21 | @Override 22 | protected void attachBaseContext(Context base) { 23 | // Wrap the Activity context 24 | super.attachBaseContext(TxNative.wrap(base)); 25 | } 26 | 27 | @Override 28 | public Resources getResources() { 29 | // Calling "getBaseContext().getResources()", instead of "super.getResources()", returns the 30 | // resources straight from TxResources and makes sure that the underlying assets are updated. 31 | // "super.getResources()" returns a cached resources object which may contain older assets. 32 | return getBaseContext().getResources(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/activity/TxBaseAppCompatActivity.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.activity; 2 | 3 | import com.transifex.txnative.TxNative; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | import androidx.appcompat.app.AppCompatDelegate; 8 | 9 | /** 10 | * A base activity that extends AppCompatActivity and implements context wrapping so that the 11 | * TxNative functionality is enabled. 12 | *

13 | * Make sure your activities extend this class or have the same implementation. 14 | */ 15 | public class TxBaseAppCompatActivity extends AppCompatActivity { 16 | 17 | private AppCompatDelegate mAppCompatDelegate; 18 | 19 | @NonNull 20 | @Override 21 | public AppCompatDelegate getDelegate() { 22 | // Wrap AppCompat delegate 23 | if (mAppCompatDelegate == null) { 24 | mAppCompatDelegate = TxNative.wrapAppCompatDelegate(super.getDelegate(), this); 25 | } 26 | return mAppCompatDelegate; 27 | } 28 | 29 | // If your app uses AppCompat 1.3 or older and 30 | // "AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);" is set , uncomment the 31 | // following lines. Otherwise, TxNative functionality will be impaired when running on older 32 | // platforms (< API 21). 33 | // 34 | // Starting from AppCompat 1.4.0, "setCompatVectorFromResourcesEnabled()" does not need this 35 | // workaround. 36 | 37 | // private Resources mResources; // final resources (VectorEnabledTintResourcesWrapper or TxResources) 38 | // 39 | // @SuppressLint("RestrictedApi") 40 | // @Override 41 | // public Resources getResources() { 42 | // Resources resources = getBaseContext().getResources(); 43 | // if (mResources == null && VectorEnabledTintResources.shouldBeUsed()) { 44 | // // We wrap the Resources returned by the base context in VectorEnabledTintResourcesWrapper, 45 | // // similarly to what AppCompatActivity does. However, these resources are not always 46 | // // the same as the ones used internally by AppCompatActivity, but that's the best 47 | // // we can do. 48 | // mResources = new VectorEnabledTintResourcesWrapper(this, resources); 49 | // } 50 | // return mResources == null ? resources : mResources; 51 | // } 52 | } 53 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxCache.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import com.transifex.common.LocaleData; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | /** 9 | * An interface for classes that act as cache for translations. 10 | */ 11 | public interface TxCache { 12 | 13 | /** 14 | * Gets all translations from the cache in the form of a 15 | * {@link LocaleData.TranslationMap TranslationMap} object. 16 | *

17 | * The returned object should not be altered as the cache may use it internally. 18 | */ 19 | @NonNull LocaleData.TranslationMap get(); 20 | 21 | /** 22 | * Get the translation for a certain key and locale pair. 23 | * 24 | * @param key The key of the string. 25 | * @param locale The locale code. 26 | * 27 | * @return The string if the key was found in the cache; null if the provided locale 28 | * does not exist in the cache or the key does not exist for this locale; empty string if the 29 | * string has not yet been translated for this locale 30 | */ 31 | @Nullable String get(@NonNull String key, @NonNull String locale); 32 | 33 | /** 34 | * Update the cache with the provided 35 | * {@link LocaleData.TranslationMap TranslationMap}. 36 | *

37 | * The translation map should not be changed after providing it to the cache, because the cache 38 | * implementation may use it without making a copy. 39 | */ 40 | void update(@NonNull LocaleData.TranslationMap translationMap); 41 | } 42 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxDecoratorCache.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import com.transifex.common.LocaleData; 4 | 5 | import java.util.Set; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | 10 | /** 11 | * Decorator class managing an internal cache and propagating the get() and update() protocol method 12 | * calls to said cache. The class should be extended to add new capabilities. 13 | */ 14 | public class TxDecoratorCache implements TxCache { 15 | 16 | protected final TxCache mInternalCache; 17 | 18 | /** 19 | * Creates a decorator with the provided cache. 20 | * 21 | * @param internalCache The cache to be used. 22 | */ 23 | public TxDecoratorCache(@NonNull TxCache internalCache) { 24 | mInternalCache = internalCache; 25 | } 26 | 27 | @NonNull 28 | @Override 29 | public LocaleData.TranslationMap get() { 30 | return mInternalCache.get(); 31 | } 32 | 33 | @Nullable 34 | @Override 35 | public String get(@NonNull String key, @NonNull String locale) { 36 | return mInternalCache.get(key, locale); 37 | } 38 | 39 | @Override 40 | public void update(@NonNull LocaleData.TranslationMap translationMap) { 41 | mInternalCache.update(translationMap); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxDiskTranslationsProvider.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import android.content.res.AssetManager; 4 | import android.util.Log; 5 | 6 | import com.transifex.common.LocaleData; 7 | import com.transifex.common.TranslationMapStorage; 8 | import com.transifex.txnative.TranslationMapStorageAndroid; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | 16 | /** 17 | * Translations provider that loads translations from disk or the application's raw asset files 18 | * depending on the constructor used. 19 | *

20 | * The directory should contain the translations in the format detailed in 21 | * {@link TranslationMapStorage}. 22 | *

23 | * If an error occurs during initialization, {@link #getTranslations()} will return 24 | * null. 25 | */ 26 | public class TxDiskTranslationsProvider implements TxTranslationsProvider { 27 | 28 | public static final String TAG = TxDiskTranslationsProvider.class.getSimpleName(); 29 | 30 | private final LocaleData.TranslationMap mTranslations; 31 | 32 | /** 33 | * Initializes the provider with a file directory containing translations and loads them 34 | * synchronously. 35 | * 36 | * @param srcDirectory The directory containing translations in the expected format. 37 | */ 38 | public TxDiskTranslationsProvider(@NonNull File srcDirectory) { 39 | // Make a check to avoid TranslationMapStorage complaining about directory not existing. 40 | if (!srcDirectory.isDirectory()) { 41 | Log.d(TAG, "Translations directory does not exist yet: " + srcDirectory.getPath()); 42 | mTranslations = null; 43 | return; 44 | } 45 | 46 | TranslationMapStorage storage = new TranslationMapStorage(TranslationMapStorage.DEFAULT_TRANSLATION_FILENAME); 47 | mTranslations = storage.fromDisk(srcDirectory); 48 | } 49 | 50 | /** 51 | * Initializes the provider with a directory under the application's raw asset files and loads 52 | * the translations synchronously. 53 | * 54 | * @param manager An asset manager instance. 55 | * @param srcDirectoryPath The path to the directory containing translations in the expected 56 | * format. 57 | */ 58 | public TxDiskTranslationsProvider(@NonNull AssetManager manager, @NonNull String srcDirectoryPath) { 59 | // Make a check and print a debug log 60 | boolean dirContainsTranslations = false; 61 | try { 62 | String[] files = manager.list(srcDirectoryPath); 63 | dirContainsTranslations = files.length != 0; 64 | } catch (IOException ignored) { 65 | } 66 | if (!dirContainsTranslations) { 67 | Log.d(TAG, "No translations exist in the Assets folder: " + srcDirectoryPath); 68 | mTranslations = null; 69 | return; 70 | } 71 | 72 | TranslationMapStorageAndroid storage = new TranslationMapStorageAndroid(manager, 73 | TranslationMapStorage.DEFAULT_TRANSLATION_FILENAME); 74 | mTranslations = storage.fromAssetsDirectory(srcDirectoryPath); 75 | } 76 | 77 | @Override 78 | @Nullable 79 | public LocaleData.TranslationMap getTranslations() { 80 | return mTranslations; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxFileOutputCacheDecorator.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import android.util.Log; 4 | 5 | import com.transifex.common.LocaleData; 6 | import com.transifex.common.TranslationMapStorage; 7 | import com.transifex.common.Utils; 8 | 9 | import java.io.File; 10 | import java.util.HashMap; 11 | import java.util.concurrent.Executor; 12 | import java.util.concurrent.Executors; 13 | import java.util.concurrent.RejectedExecutionException; 14 | 15 | import androidx.annotation.NonNull; 16 | import androidx.annotation.Nullable; 17 | 18 | /** 19 | * Decorator class responsible for storing any updates of the translations to a directory specified 20 | * in the constructor. 21 | *

22 | * Storing the translations happens asynchronously on a background thread after 23 | * {@link #update(LocaleData.TranslationMap)} is called. 24 | */ 25 | public class TxFileOutputCacheDecorator extends TxDecoratorCache { 26 | 27 | public static final String TAG = TxFileOutputCacheDecorator.class.getSimpleName(); 28 | 29 | private final File mDstDirectory; 30 | private final Executor mExecutor; 31 | 32 | /** 33 | * Creates a new instance with a specific directory for storing the translations to the disk 34 | * and an internal cache. 35 | * 36 | * @param dstDirectory The destination directory to write the translations to when the 37 | * {@link #update(LocaleData.TranslationMap)} is called. 38 | * @param internalCache The internal cache. 39 | */ 40 | public TxFileOutputCacheDecorator(@NonNull File dstDirectory, @NonNull TxCache internalCache) { 41 | this(null, dstDirectory, internalCache); 42 | } 43 | 44 | 45 | /** 46 | * Creates a new instance with a specific directory for storing the translations to the disk 47 | * and an internal cache. 48 | * 49 | * @param executor The executor that will run the IO operations; if null is 50 | * provided, {@link Executors#newSingleThreadExecutor()} is used. 51 | * @param dstDirectory The destination directory to write the translations to when the 52 | * {@link #update(LocaleData.TranslationMap)} is called. 53 | * @param internalCache The internal cache. 54 | */ 55 | protected TxFileOutputCacheDecorator(@Nullable Executor executor, @NonNull File dstDirectory, 56 | @NonNull TxCache internalCache) { 57 | super(internalCache); 58 | mExecutor = (executor != null) ? executor : Executors.newSingleThreadExecutor(); 59 | mDstDirectory = dstDirectory; 60 | } 61 | 62 | /** 63 | * Updates the cache with the provided translations and writes them to the specified directory 64 | * after clearing its content, if any. 65 | *

66 | * For the serialization and writing of the translations on disk, {@link TranslationMapStorage} 67 | * is used internally. Each translation file uses "txstrings.json" as filename. Unlike 68 | * TranslationMapStorage, pre-existing translations are not kept. 69 | */ 70 | @Override 71 | public void update(@NonNull final LocaleData.TranslationMap translationMap) { 72 | super.update(translationMap); 73 | 74 | try { 75 | mExecutor.execute(new Runnable() { 76 | @Override 77 | public void run() { 78 | // Delete existing translation files 79 | if (mDstDirectory.isDirectory()) { 80 | Utils.deleteDirectoryContents(mDstDirectory); 81 | } 82 | // Write translation files 83 | TranslationMapStorage storage = new TranslationMapStorage(TranslationMapStorage.DEFAULT_TRANSLATION_FILENAME); 84 | HashMap files = storage.toDisk(translationMap, mDstDirectory); 85 | } 86 | }); 87 | } 88 | catch (RejectedExecutionException exception) { 89 | Log.e(TAG, "Could not store updated translations: " + exception); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxMemoryCache.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import com.transifex.common.LocaleData; 4 | 5 | import java.util.Set; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | 10 | /** 11 | * A cache that holds translations in memory. 12 | */ 13 | public class TxMemoryCache implements TxCache { 14 | 15 | private LocaleData.TranslationMap mTranslationMap = new LocaleData.TranslationMap(0); 16 | 17 | @NonNull 18 | @Override 19 | public LocaleData.TranslationMap get() { 20 | return mTranslationMap; 21 | } 22 | 23 | @Nullable 24 | @Override 25 | public String get(@NonNull String key, @NonNull String locale) { 26 | LocaleData.LocaleStrings localeStrings = mTranslationMap.get(locale); 27 | if (localeStrings == null) { 28 | return null; 29 | } 30 | return localeStrings.get(key); 31 | } 32 | 33 | @Override 34 | public void update(@NonNull LocaleData.TranslationMap translationMap) { 35 | mTranslationMap = translationMap; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxProviderBasedCache.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import com.transifex.common.LocaleData; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | /** 8 | * Composite class that accepts a number of translations providers and an internal cache. When 9 | * initialized, the providers are used to update the internal class in the order they are added in 10 | * the providers array. 11 | *

12 | * Example usage: 13 | *

14 |  * TxCache cache = new TxProviderBasedCache(
15 |  *         new TxDiskTranslationsProvider[]{
16 |  *                 new TxDiskTranslationsProvider(firstTranslationsDirectory),
17 |  *                 new TxDiskTranslationsProvider(secondTranslationsDirectory)},
18 |  *         new TxMemoryCache());
19 |  * 
20 | */ 21 | public class TxProviderBasedCache extends TxDecoratorCache { 22 | 23 | /** 24 | * Creates a provider-based cache with the given internal cache and updates it with with the 25 | * contents of the given translations providers. 26 | *

27 | * The translations providers update the internal cache in the given order. If they return a 28 | * null or empty {@link LocaleData.TranslationMap} they are 29 | * ignored. 30 | *

31 | * The providers' content is accessed synchronously in the class's constructor. 32 | * 33 | * @param providers An array of translations providers. 34 | * @param internalCache The internal cache to be used. 35 | */ 36 | public TxProviderBasedCache(@NonNull TxTranslationsProvider[] providers, @NonNull TxCache internalCache) { 37 | super(internalCache); 38 | 39 | for (TxTranslationsProvider provider : providers) { 40 | LocaleData.TranslationMap translations = provider.getTranslations(); 41 | if (translations != null && !translations.isEmpty()) { 42 | mInternalCache.update(provider.getTranslations()); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxReadonlyCacheDecorator.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import com.transifex.common.LocaleData; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | /** 8 | * Decorator class that makes the internal cache read-only so that no update operations are allowed. 9 | */ 10 | public class TxReadonlyCacheDecorator extends TxDecoratorCache { 11 | 12 | public TxReadonlyCacheDecorator(@NonNull TxCache internalCache) { 13 | super(internalCache); 14 | } 15 | 16 | /** 17 | * This method is a no-op as this cache decorator is read-only. 18 | */ 19 | @Override 20 | public void update(@NonNull LocaleData.TranslationMap translationMap) { 21 | // No-op 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxStandardCache.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import android.content.Context; 4 | 5 | import com.transifex.common.TranslationMapStorage; 6 | 7 | import java.io.File; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.Nullable; 11 | 12 | /** 13 | * The standard cache configuration that the TxNative SDK is initialized with, if no other cache is 14 | * provided. 15 | *

16 | * TxStandardCache is backed by a {@link TxMemoryCache} which is initialized with existing translation 17 | * files from the app's Assets folder and the app's cache directory in this specific order. When 18 | * the cache is updated (when new translations become available after calling 19 | * {@link com.transifex.txnative.TxNative#fetchTranslations(String)}), TxStandardCache stores the new 20 | * translations in the app's cache directory. The in-memory translations are not affected by the 21 | * update though. A new instance of TxStandardCache has to be created (after an app restart by default) 22 | * to read the new translations saved in the app's cache directory 23 | */ 24 | public class TxStandardCache { 25 | 26 | /** 27 | * Creates a cache with the configuration explained in {@link TxStandardCache}. 28 | * 29 | * @param context The app's context. 30 | * @param updatePolicy The update policy to be used when initializing the internal memory 31 | * cache with the stored contents from disk. If set to null, 32 | * {@link TxUpdateFilterCache.TxCacheUpdatePolicy#REPLACE_ALL REPLACE_ALL} is used. 33 | * @param cachedTranslationsDirectory The directory where the cache will store new translations 34 | * when available and read translations from when initialized. 35 | * If set to null it uses a "txnative" folder in 36 | * the app's internal cache directory. 37 | * 38 | * @return A TxCache instance. 39 | */ 40 | public static TxCache getCache(@NonNull Context context, 41 | @Nullable @TxUpdateFilterCache.TxCacheUpdatePolicy Integer updatePolicy, 42 | @Nullable File cachedTranslationsDirectory) { 43 | 44 | if (updatePolicy == null) { 45 | updatePolicy = TxUpdateFilterCache.TxCacheUpdatePolicy.REPLACE_ALL; 46 | } 47 | if (cachedTranslationsDirectory == null) { 48 | cachedTranslationsDirectory = new File(context.getCacheDir() + File.separator 49 | + TranslationMapStorage.DEFAULT_TRANSLATIONS_DIR_NAME); 50 | } 51 | 52 | TxTranslationsProvider[] providers = new TxDiskTranslationsProvider[] { 53 | new TxDiskTranslationsProvider( 54 | context.getAssets(), 55 | TranslationMapStorage.DEFAULT_TRANSLATIONS_DIR_NAME), 56 | new TxDiskTranslationsProvider(cachedTranslationsDirectory) 57 | }; 58 | 59 | return new TxFileOutputCacheDecorator( 60 | cachedTranslationsDirectory, 61 | new TxReadonlyCacheDecorator( 62 | new TxProviderBasedCache( 63 | providers, 64 | new TxUpdateFilterCache( 65 | updatePolicy, 66 | new TxMemoryCache() 67 | ) 68 | ) 69 | ) 70 | ); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxTranslationsProvider.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import com.transifex.common.LocaleData; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | /** 8 | * An interface for classes that act as providers of translations (e.g. extracting them from a file) 9 | */ 10 | public interface TxTranslationsProvider { 11 | 12 | /** 13 | * Returns the translations from the provider. 14 | * 15 | * @return A {@link LocaleData.TranslationMap} object or null if an error occurred. 16 | * The returned map can be empty if an error occurred. 17 | */ 18 | @Nullable 19 | LocaleData.TranslationMap getTranslations(); 20 | } 21 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/missingpolicy/AndroidMissingPolicy.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.missingpolicy; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.PluralsRes; 8 | import androidx.annotation.StringRes; 9 | 10 | /** 11 | * Returns a translated string using Android's localization system. 12 | *

13 | * You can use this policy to fall back to translations provided via strings.xml 14 | * when a translation string can't be provided by TxNative's cache. 15 | */ 16 | public class AndroidMissingPolicy implements MissingPolicy{ 17 | 18 | 19 | public AndroidMissingPolicy(){ 20 | 21 | } 22 | 23 | /** 24 | * Creates a new instance. 25 | *

26 | * This constructor has been deprecated. A context is no longer needed. 27 | * 28 | * @param applicationContext The application context. Do not provide a context wrapped by 29 | * {@link com.transifex.txnative.TxNative#wrap(Context) TxNative#wrap(Context)}. 30 | */ 31 | @Deprecated 32 | public AndroidMissingPolicy(@NonNull Context applicationContext) { 33 | } 34 | 35 | /** 36 | * Returns a translated string using Android's localization system. 37 | *

38 | * The result is equivalent to calling {@link android.content.res.Resources#getText(int)} 39 | * without using TxNative functionality. 40 | */ 41 | @Override 42 | @NonNull public CharSequence get(@NonNull Resources resources, 43 | @NonNull CharSequence sourceString, @StringRes int id, 44 | @NonNull String resourceName, @NonNull String locale) { 45 | return resources.getText(id); 46 | } 47 | 48 | /** 49 | * Returns a translated quantity string using Android's localization system. 50 | *

51 | * The result is equivalent to calling {@link android.content.res.Resources#getQuantityText(int, int)} 52 | * without using TxNative functionality. 53 | */ 54 | @Override 55 | @NonNull public CharSequence getQuantityString(@NonNull Resources resources, 56 | @NonNull CharSequence sourceQuantityString, @PluralsRes int id, int quantity, 57 | @NonNull String resourceName, @NonNull String locale) { 58 | return resources.getQuantityText(id, quantity); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/missingpolicy/CompositeMissingPolicy.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.missingpolicy; 2 | 3 | import android.content.res.Resources; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.PluralsRes; 7 | import androidx.annotation.StringRes; 8 | 9 | /** 10 | * Combines multiple policies to create a complex result. 11 | *

12 | * The result of each policy is fed to the next policy as source. 13 | */ 14 | public class CompositeMissingPolicy implements MissingPolicy{ 15 | 16 | private final MissingPolicy[] mMissingPolicies; 17 | 18 | /** 19 | * Creates a new instance with the provided missing policies. 20 | *

21 | * The order of the missing policies is important; the result of each policy is fed to the next 22 | * policy as source. 23 | * 24 | * @param missingPolicies The missing policies to be used. 25 | */ 26 | public CompositeMissingPolicy(@NonNull MissingPolicy[] missingPolicies) { 27 | mMissingPolicies = missingPolicies; 28 | } 29 | 30 | /** 31 | * Returns a string after it has been fed to all of the provided policies from first to last. 32 | */ 33 | @Override 34 | @NonNull public CharSequence get(@NonNull Resources resources, 35 | @NonNull CharSequence sourceString, @StringRes int id, 36 | @NonNull String resourceName, @NonNull String locale) { 37 | CharSequence string = sourceString; 38 | for (MissingPolicy policy : mMissingPolicies) { 39 | string = policy.get(resources, string, id, resourceName, locale); 40 | } 41 | 42 | return string; 43 | } 44 | 45 | /** 46 | * Returns a quantity string after it has been fed to all of the provided policies from first 47 | * to last. 48 | */ 49 | @Override 50 | @NonNull public CharSequence getQuantityString(@NonNull Resources resources, 51 | @NonNull CharSequence sourceQuantityString, @PluralsRes int id, int quantity, 52 | @NonNull String resourceName, @NonNull String locale) { 53 | CharSequence quantityString = sourceQuantityString; 54 | for (MissingPolicy policy : mMissingPolicies) { 55 | quantityString = policy.getQuantityString(resources, quantityString, id, quantity, 56 | resourceName, locale); 57 | } 58 | 59 | return quantityString; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/missingpolicy/MissingPolicy.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.missingpolicy; 2 | 3 | import android.content.res.Resources; 4 | 5 | import com.transifex.txnative.LocaleState; 6 | import com.transifex.txnative.TxResources; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.PluralsRes; 10 | import androidx.annotation.StringRes; 11 | 12 | /** 13 | * An interface for classes that determine what translation is returned when the requested 14 | * translation is not available. 15 | */ 16 | public interface MissingPolicy { 17 | 18 | /** 19 | * Return a string as a translation based on the given source string. 20 | *

21 | * Classes that implement this interface may choose to return anything relevant to the given 22 | * source string or not, based on their custom policy. 23 | * 24 | * @param resources A Resources object. This is the base resources object returned by 25 | * {@link TxResources#getBaseResources()}. 26 | * @param sourceString The source string. 27 | * @param id The string resource identifier as defined by 28 | * {@link Resources#getIdentifier(String, String, String)}. 29 | * @param resourceName The entry name of the string resource as defined by 30 | * {@link Resources#getResourceEntryName(int)}. 31 | * @param locale The current locale as returned by {@link LocaleState#getResolvedLocale()}. 32 | * 33 | * @return The translated string. 34 | */ 35 | @NonNull CharSequence get(@NonNull Resources resources, @NonNull CharSequence sourceString, 36 | @StringRes int id, @NonNull String resourceName, @NonNull String locale); 37 | 38 | /** 39 | * Return a quantity string as a translation based on the given source quantity string and 40 | * quantity. 41 | *

42 | * Classes that implement this interface may choose to return anything relevant to the given 43 | * source string or not, based on their custom policy. 44 | * 45 | * @param resources A Resources object. This is the base resources object returned by 46 | * {@link TxResources#getBaseResources()}. 47 | * @param sourceQuantityString The source string having grammatically correct pluralization for 48 | * the given quantity. 49 | * @param id The plurals resource identifier as defined by 50 | * {@link Resources#getIdentifier(String, String, String)}. 51 | * @param quantity The number used to get the correct string for the current language's plural 52 | * rules. 53 | * @param resourceName The entry name of the plurals resource as defined by 54 | * {@link Resources#getResourceEntryName(int)}. 55 | * @param locale The current locale as returned by {@link LocaleState#getResolvedLocale()}. 56 | * 57 | * @return The translated string. 58 | */ 59 | @NonNull CharSequence getQuantityString(@NonNull Resources resources, 60 | @NonNull CharSequence sourceQuantityString, 61 | @PluralsRes int id, int quantity, 62 | @NonNull String resourceName, @NonNull String locale); 63 | } 64 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/missingpolicy/SourceStringPolicy.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.missingpolicy; 2 | 3 | import android.content.res.Resources; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.PluralsRes; 7 | import androidx.annotation.StringRes; 8 | 9 | /** 10 | * Returns the source string when the translation string is missing. 11 | */ 12 | public class SourceStringPolicy implements MissingPolicy { 13 | 14 | /** 15 | * Return the source string as the translation string. 16 | */ 17 | @Override 18 | @NonNull public CharSequence get(@NonNull Resources resources, 19 | @NonNull CharSequence sourceString, @StringRes int id, 20 | @NonNull String resourceName, @NonNull String locale) { 21 | return sourceString; 22 | } 23 | 24 | /** 25 | * Returns the source quantity string as the translation quantity string. 26 | */ 27 | @Override 28 | @NonNull public CharSequence getQuantityString(@NonNull Resources resources, 29 | @NonNull CharSequence sourceQuantityString, @PluralsRes int id, int quantity, 30 | @NonNull String resourceName, @NonNull String locale) { 31 | return sourceQuantityString; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/missingpolicy/WrappedStringPolicy.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.missingpolicy; 2 | 3 | 4 | import android.content.res.Resources; 5 | import android.text.SpannableStringBuilder; 6 | import android.text.Spanned; 7 | import android.text.SpannedString; 8 | import android.text.TextUtils; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.Nullable; 12 | import androidx.annotation.PluralsRes; 13 | import androidx.annotation.StringRes; 14 | 15 | /** 16 | * Wraps the source string with a custom format. 17 | *

18 | * Example: 19 | *

20 | * {@code new WrappedStringPolicy(">>", "<<").get("Click here");} 21 | *

22 | * Returns: 23 | *

24 | * {@code ">>Click here<<"} 25 | */ 26 | public class WrappedStringPolicy implements MissingPolicy{ 27 | 28 | private final String start; 29 | private final String end; 30 | private final int length; 31 | 32 | /** 33 | * Creates a new instance with the provided strings. 34 | * 35 | * @param start The string to prepend the source String. Can be null. 36 | * @param end The string to append to the source string. Can be null. 37 | */ 38 | public WrappedStringPolicy(@Nullable String start, @Nullable String end) { 39 | this.start = start; 40 | this.end = end; 41 | 42 | int length = 0; 43 | if (!TextUtils.isEmpty(start)) { 44 | length += start.length(); 45 | } 46 | if (!TextUtils.isEmpty(end)) { 47 | length += end.length(); 48 | } 49 | this.length = length; 50 | } 51 | 52 | /** 53 | * Wraps the provided sourceString with the start and end strings. 54 | *

55 | * If sourceString is {@link Spanned}, a {@link SpannedString} containing the same spans is 56 | * returned. 57 | */ 58 | @NonNull CharSequence wrapString(@NonNull CharSequence sourceString) { 59 | if (TextUtils.isEmpty(start) && TextUtils.isEmpty(end)) { 60 | return sourceString; 61 | } 62 | 63 | boolean isSpanned = sourceString instanceof Spanned; 64 | if (isSpanned) { 65 | SpannableStringBuilder sb = new SpannableStringBuilder(); 66 | if (!TextUtils.isEmpty(start)) { 67 | sb.append(start); 68 | } 69 | sb.append(sourceString); 70 | if (!TextUtils.isEmpty(end)) { 71 | sb.append(end); 72 | } 73 | return new SpannedString(sb); 74 | } 75 | else { 76 | StringBuilder sb = new StringBuilder(sourceString.length() + length); 77 | if (!TextUtils.isEmpty(start)) { 78 | sb.append(start); 79 | } 80 | sb.append(sourceString); 81 | if (!TextUtils.isEmpty(end)) { 82 | sb.append(end); 83 | } 84 | return sb.toString(); 85 | } 86 | } 87 | 88 | /** 89 | * Returns a wrapped string. 90 | */ 91 | @Override 92 | @NonNull public CharSequence get(@NonNull Resources resources, 93 | @NonNull CharSequence sourceString, @StringRes int id, 94 | @NonNull String resourceName, @NonNull String locale) { 95 | return wrapString(sourceString); 96 | } 97 | 98 | /** 99 | * Returns a wrapped quantity string. 100 | */ 101 | @Override 102 | @NonNull public CharSequence getQuantityString(@NonNull Resources resources, 103 | @NonNull CharSequence sourceQuantityString, @PluralsRes int id, int quantity, 104 | @NonNull String resourceName, @NonNull String locale) { 105 | return wrapString(sourceQuantityString); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/transformers/SupportToolbarTransformer.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.transformers; 2 | 3 | 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | 8 | import com.transifex.txnative.Utils; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.appcompat.widget.Toolbar; 12 | 13 | public class SupportToolbarTransformer extends ViewTransformer { 14 | 15 | @Override 16 | public void transform(@NonNull Context context, @NonNull View view, @NonNull AttributeSet attrs) { 17 | super.transform(context, view, attrs); 18 | 19 | Toolbar toolbar = (Toolbar) view; 20 | 21 | int titleResourceId = Utils.getStringResourceId(context, attrs, android.R.attr.title); 22 | int titleCompatResourceId = Utils.getStringResourceId(context, attrs, androidx.appcompat.R.attr.title); 23 | if (titleResourceId != 0) { 24 | toolbar.setTitle(titleResourceId); 25 | } 26 | else if (titleCompatResourceId != 0) { 27 | toolbar.setTitle(titleCompatResourceId); 28 | } 29 | 30 | int subtitleResourceId = Utils.getStringResourceId(context, attrs, android.R.attr.subtitle); 31 | int subtitleCompatResourceId = Utils.getStringResourceId(context, attrs, androidx.appcompat.R.attr.subtitle); 32 | if (subtitleResourceId != 0) { 33 | toolbar.setSubtitle(subtitleResourceId); 34 | } 35 | else if (subtitleCompatResourceId != 0) { 36 | toolbar.setSubtitle(subtitleCompatResourceId); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/transformers/TextInputLayoutTransformer.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.transformers; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | 7 | import com.google.android.material.textfield.TextInputLayout; 8 | import com.transifex.txnative.Utils; 9 | 10 | import androidx.annotation.NonNull; 11 | 12 | public class TextInputLayoutTransformer extends ViewTransformer{ 13 | 14 | @Override 15 | public void transform(@NonNull Context context, @NonNull View view, @NonNull AttributeSet attrs) { 16 | super.transform(context, view, attrs); 17 | 18 | TextInputLayout textInputLayout = (TextInputLayout) view; 19 | 20 | int hintResourceId = Utils.getStringResourceId(context, attrs, android.R.attr.hint); 21 | if (hintResourceId != 0) { 22 | textInputLayout.setHint(hintResourceId); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/transformers/TextViewTransformer.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.transformers; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import com.transifex.txnative.Utils; 9 | 10 | import androidx.annotation.NonNull; 11 | 12 | public class TextViewTransformer extends ViewTransformer { 13 | 14 | @Override 15 | public void transform(@NonNull Context context, @NonNull View view, @NonNull AttributeSet attrs) { 16 | super.transform(context, view, attrs); 17 | 18 | TextView textView = (TextView) view; 19 | 20 | int textResourceId = Utils.getStringResourceId(context, attrs, android.R.attr.text); 21 | if (textResourceId != 0) { 22 | //String textResourceName = context.getResources().getResourceEntryName(textResourceId); 23 | 24 | // This will be handled by our overridden resources 25 | textView.setText(textResourceId); 26 | } 27 | 28 | int hintResourceId = Utils.getStringResourceId(context, attrs, android.R.attr.hint); 29 | if (hintResourceId != 0) { 30 | textView.setHint(hintResourceId); 31 | } 32 | } 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/transformers/ToolbarTransformer.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.transformers; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.widget.Toolbar; 8 | 9 | import com.transifex.txnative.Utils; 10 | 11 | import androidx.annotation.NonNull; 12 | import androidx.annotation.RequiresApi; 13 | 14 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 15 | public class ToolbarTransformer extends ViewTransformer { 16 | 17 | 18 | @Override 19 | public void transform(@NonNull Context context, @NonNull View view, @NonNull AttributeSet attrs) { 20 | super.transform(context, view, attrs); 21 | 22 | Toolbar toolbar = (Toolbar) view; 23 | 24 | CharSequence xa = toolbar.getTitle(); 25 | 26 | int titleResourceId = Utils.getStringResourceId(context, attrs, android.R.attr.title); 27 | int titleCompatResourceId = 0; 28 | if (Utils.isAppcompatPresent()) { 29 | titleCompatResourceId = Utils.getStringResourceId(context, attrs, androidx.appcompat.R.attr.title); 30 | } 31 | if (titleResourceId != 0) { 32 | toolbar.setTitle(titleResourceId); 33 | } 34 | else if (titleCompatResourceId != 0) { 35 | toolbar.setTitle(titleCompatResourceId); 36 | } 37 | 38 | 39 | int subtitleResourceId = Utils.getStringResourceId(context, attrs, android.R.attr.subtitle); 40 | int subtitleCompatResourceId = 0; 41 | if (Utils.isAppcompatPresent()) { 42 | subtitleCompatResourceId = Utils.getStringResourceId(context, attrs, androidx.appcompat.R.attr.subtitle); 43 | } 44 | if (subtitleResourceId != 0) { 45 | toolbar.setSubtitle(subtitleResourceId); 46 | } 47 | else if (subtitleCompatResourceId != 0) { 48 | toolbar.setSubtitle(subtitleCompatResourceId); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/transformers/ViewTransformer.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.transformers; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | 7 | import com.transifex.txnative.Utils; 8 | 9 | import androidx.annotation.NonNull; 10 | 11 | public class ViewTransformer { 12 | 13 | public void transform(@NonNull Context context, @NonNull View view, @NonNull AttributeSet attrs) { 14 | int contentDescriptionId = Utils.getStringResourceId(context, attrs, android.R.attr.contentDescription); 15 | if (contentDescriptionId != 0) { 16 | view.setContentDescription(context.getString(contentDescriptionId)); 17 | } 18 | 19 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { 20 | int tooltipTextId = Utils.getStringResourceId(context, attrs, android.R.attr.tooltipText); 21 | if (tooltipTextId != 0) { 22 | view.setTooltipText(context.getString(tooltipTextId)); 23 | } 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/wrappers/TxContextWrapper.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.wrappers; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.content.ContextWrapper; 6 | import android.content.res.Resources; 7 | 8 | import com.transifex.txnative.NativeCore; 9 | import com.transifex.txnative.TxResources; 10 | 11 | import androidx.annotation.NonNull; 12 | 13 | /** 14 | * Context wrapper that enables TxNative functionality by wrapping the base resources with 15 | * {@link TxResources}. 16 | */ 17 | public class TxContextWrapper extends ContextWrapper { 18 | 19 | private Resources mWrappedResources; 20 | private final NativeCore mNativeCore; 21 | 22 | public TxContextWrapper(@NonNull Context base, @NonNull NativeCore nativeCore) { 23 | super(base); 24 | mNativeCore = nativeCore; 25 | } 26 | 27 | @SuppressLint("RestrictedApi") 28 | @Override 29 | public Resources getResources() { 30 | if (mWrappedResources == null) { 31 | mWrappedResources = new TxResources(super.getResources(), mNativeCore); 32 | } 33 | return mWrappedResources; 34 | } 35 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/wrappers/TxContextWrappingDelegate2.kt: -------------------------------------------------------------------------------- 1 | package androidx.appcompat.app; 2 | 3 | import android.content.Context 4 | import android.content.res.Configuration 5 | import android.os.Bundle 6 | import android.util.AttributeSet 7 | import android.view.MenuInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import androidx.appcompat.view.ActionMode 11 | import androidx.appcompat.widget.Toolbar 12 | import com.transifex.txnative.TxNative 13 | 14 | // This class makes resource interception work in Appcompat 1.2.0 and later 15 | // https://github.com/JcMinarro/Philology/issues/35 16 | // https://stackoverflow.com/questions/55265834/change-locale-not-work-after-migrate-to-androidx/58004553#58004553 17 | 18 | /** 19 | * Wrapper of AppcompatDelegate that enables TxNative functionality by wrapping the base context. 20 | */ 21 | class TxContextWrappingDelegate2(private val superDelegate: AppCompatDelegate) : AppCompatDelegate() { 22 | 23 | override fun getSupportActionBar() = superDelegate.supportActionBar 24 | 25 | override fun setSupportActionBar(toolbar: Toolbar?) = superDelegate.setSupportActionBar(toolbar) 26 | 27 | override fun getMenuInflater(): MenuInflater? = superDelegate.menuInflater 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | superDelegate.onCreate(savedInstanceState) 31 | removeActivityDelegate(superDelegate) 32 | addActiveDelegate(this) 33 | } 34 | 35 | override fun onPostCreate(savedInstanceState: Bundle?) = superDelegate.onPostCreate(savedInstanceState) 36 | 37 | override fun onConfigurationChanged(newConfig: Configuration?) = superDelegate.onConfigurationChanged(newConfig) 38 | 39 | override fun onStart() = superDelegate.onStart() 40 | 41 | override fun onStop() = superDelegate.onStop() 42 | 43 | override fun onPostResume() = superDelegate.onPostResume() 44 | 45 | override fun setTheme(themeResId: Int) = superDelegate.setTheme(themeResId) 46 | 47 | override fun findViewById(id: Int) = superDelegate.findViewById(id) 48 | 49 | override fun setContentView(v: View?) = superDelegate.setContentView(v) 50 | 51 | override fun setContentView(resId: Int) = superDelegate.setContentView(resId) 52 | 53 | override fun setContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.setContentView(v, lp) 54 | 55 | override fun addContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.addContentView(v, lp) 56 | 57 | override fun attachBaseContext2(context: Context) = wrap(superDelegate.attachBaseContext2(super.attachBaseContext2(context))) 58 | 59 | override fun setTitle(title: CharSequence?) = superDelegate.setTitle(title) 60 | 61 | override fun invalidateOptionsMenu() = superDelegate.invalidateOptionsMenu() 62 | 63 | override fun onDestroy() { 64 | superDelegate.onDestroy() 65 | removeActivityDelegate(this) 66 | } 67 | 68 | override fun getDrawerToggleDelegate() = superDelegate.drawerToggleDelegate 69 | 70 | override fun requestWindowFeature(featureId: Int) = superDelegate.requestWindowFeature(featureId) 71 | 72 | override fun hasWindowFeature(featureId: Int) = superDelegate.hasWindowFeature(featureId) 73 | 74 | override fun startSupportActionMode(callback: ActionMode.Callback) = superDelegate.startSupportActionMode(callback) 75 | 76 | override fun installViewFactory() = superDelegate.installViewFactory() 77 | 78 | override fun createView(parent: View?, name: String?, context: Context, attrs: AttributeSet): View? = superDelegate.createView(parent, name, context, attrs) 79 | 80 | override fun setHandleNativeActionModesEnabled(enabled: Boolean) { 81 | superDelegate.isHandleNativeActionModesEnabled = enabled 82 | } 83 | 84 | override fun isHandleNativeActionModesEnabled() = superDelegate.isHandleNativeActionModesEnabled 85 | 86 | override fun onSaveInstanceState(outState: Bundle?) = superDelegate.onSaveInstanceState(outState) 87 | 88 | override fun applyDayNight() = superDelegate.applyDayNight() 89 | 90 | override fun setLocalNightMode(mode: Int) { 91 | superDelegate.localNightMode = mode 92 | } 93 | 94 | override fun getLocalNightMode() = superDelegate.localNightMode 95 | 96 | private fun wrap(context: Context): Context { 97 | return TxNative.wrap(context) 98 | } 99 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/wrappers/TxContextWrappingDelegateJava2.java: -------------------------------------------------------------------------------- 1 | package androidx.appcompat.app; 2 | 3 | import android.content.Context; 4 | import android.content.res.Configuration; 5 | import android.os.Bundle; 6 | import android.util.AttributeSet; 7 | import android.view.MenuInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import com.transifex.txnative.TxNative; 12 | 13 | import androidx.annotation.CallSuper; 14 | import androidx.annotation.NonNull; 15 | import androidx.annotation.Nullable; 16 | import androidx.annotation.StyleRes; 17 | import androidx.appcompat.view.ActionMode; 18 | import androidx.appcompat.widget.Toolbar; 19 | 20 | // This class makes resource interception work in Appcompat 1.2.0 21 | // https://github.com/JcMinarro/Philology/issues/35 22 | // https://stackoverflow.com/questions/55265834/change-locale-not-work-after-migrate-to-androidx/58004553#58004553 23 | 24 | /** 25 | * Wrapper of AppcompatDelegate that enables TxNative functionality by wrapping the base context. 26 | */ 27 | public class TxContextWrappingDelegateJava2 extends AppCompatDelegate { 28 | 29 | private final AppCompatDelegate superDelegate; 30 | 31 | public TxContextWrappingDelegateJava2(AppCompatDelegate superDelegate) { 32 | this.superDelegate = superDelegate; 33 | } 34 | 35 | @Nullable 36 | @Override 37 | public ActionBar getSupportActionBar() { 38 | return superDelegate.getSupportActionBar(); 39 | } 40 | 41 | @Override 42 | public void setSupportActionBar(@Nullable Toolbar toolbar) { 43 | superDelegate.setSupportActionBar(toolbar); 44 | } 45 | 46 | @Override 47 | public MenuInflater getMenuInflater() { 48 | return superDelegate.getMenuInflater(); 49 | } 50 | 51 | @Override 52 | public void onCreate(Bundle savedInstanceState) { 53 | superDelegate.onCreate(savedInstanceState); 54 | removeActivityDelegate(superDelegate); 55 | addActiveDelegate(this); 56 | } 57 | 58 | @Override 59 | public void onPostCreate(Bundle savedInstanceState) { 60 | superDelegate.onPostCreate(savedInstanceState); 61 | } 62 | 63 | @Override 64 | public void onConfigurationChanged(Configuration newConfig) { 65 | superDelegate.onConfigurationChanged(newConfig); 66 | } 67 | 68 | @Override 69 | public void onStart() { 70 | superDelegate.onStart(); 71 | } 72 | 73 | @Override 74 | public void onStop() { 75 | superDelegate.onStop(); 76 | } 77 | 78 | @Override 79 | public void onPostResume() { 80 | superDelegate.onPostResume(); 81 | } 82 | 83 | @Override 84 | public void setTheme(@StyleRes int themeResId) { 85 | superDelegate.setTheme(themeResId); 86 | } 87 | 88 | @Nullable 89 | @Override 90 | public T findViewById(int id) { 91 | return superDelegate.findViewById(id); 92 | } 93 | 94 | @Override 95 | public void setContentView(View v) { 96 | superDelegate.setContentView(v); 97 | } 98 | 99 | @Override 100 | public void setContentView(int resId) { 101 | superDelegate.setContentView(resId); 102 | } 103 | 104 | @Override 105 | public void setContentView(View v, ViewGroup.LayoutParams lp) { 106 | superDelegate.setContentView(v, lp); 107 | } 108 | 109 | @Override 110 | public void addContentView(View v, ViewGroup.LayoutParams lp) { 111 | superDelegate.addContentView(v, lp); 112 | } 113 | 114 | @Override 115 | public void setTitle(@Nullable CharSequence title) { 116 | superDelegate.setTitle(title); 117 | } 118 | 119 | @Override 120 | public void invalidateOptionsMenu() { 121 | superDelegate.invalidateOptionsMenu(); 122 | } 123 | 124 | @Override 125 | public void onDestroy() { 126 | superDelegate.onDestroy(); 127 | removeActivityDelegate(this); 128 | } 129 | 130 | @Nullable 131 | @Override 132 | public ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() { 133 | return superDelegate.getDrawerToggleDelegate(); 134 | } 135 | 136 | @Override 137 | public boolean requestWindowFeature(int featureId) { 138 | return superDelegate.requestWindowFeature(featureId); 139 | } 140 | 141 | @Override 142 | public boolean hasWindowFeature(int featureId) { 143 | return superDelegate.hasWindowFeature(featureId); 144 | } 145 | 146 | @Nullable 147 | @Override 148 | public ActionMode startSupportActionMode(@NonNull ActionMode.Callback callback) { 149 | return superDelegate.startSupportActionMode(callback); 150 | } 151 | 152 | @Override 153 | public void installViewFactory() { 154 | superDelegate.installViewFactory(); 155 | } 156 | 157 | @Override 158 | public View createView(@Nullable View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) { 159 | return superDelegate.createView(parent, name, context, attrs); 160 | } 161 | 162 | @Override 163 | public void setHandleNativeActionModesEnabled(boolean enabled) { 164 | superDelegate.setHandleNativeActionModesEnabled(enabled); 165 | } 166 | 167 | @Override 168 | public boolean isHandleNativeActionModesEnabled() { 169 | return superDelegate.isHandleNativeActionModesEnabled(); 170 | } 171 | 172 | @Override 173 | public void onSaveInstanceState(Bundle outState) { 174 | superDelegate.onSaveInstanceState(outState); 175 | } 176 | 177 | @Override 178 | public boolean applyDayNight() { 179 | return superDelegate.applyDayNight(); 180 | } 181 | 182 | @Override 183 | public void setLocalNightMode(int mode) { 184 | superDelegate.setLocalNightMode(mode); 185 | } 186 | 187 | @Override 188 | public int getLocalNightMode() { 189 | return superDelegate.getLocalNightMode(); 190 | } 191 | 192 | @NonNull 193 | @CallSuper 194 | public Context attachBaseContext2(@NonNull Context context) { 195 | return wrap(superDelegate.attachBaseContext2(super.attachBaseContext2(context))); 196 | } 197 | 198 | @NonNull 199 | private Context wrap(Context context) { 200 | return TxNative.wrap(context); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | zero 9 | one 10 | two 11 | few 12 | many 13 | other 14 | 15 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/test/java/com/transifex/txnative/LocaleStateTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Locale; 6 | 7 | import static com.google.common.truth.Truth.assertThat; 8 | 9 | public class LocaleStateTest { 10 | 11 | @Test 12 | public void testSourceLocale_nullSourceLocale() { 13 | LocaleState localeState = new LocaleState(null, null, 14 | null, 15 | new Locale("el")); 16 | 17 | assertThat(localeState.getSourceLocale()).isEqualTo("en"); 18 | } 19 | 20 | @Test 21 | public void testAppLocales_nullAppLocales() { 22 | LocaleState localeState = new LocaleState(null, "en", 23 | null, 24 | new Locale("el")); 25 | 26 | assertThat(localeState.getAppLocales()).asList().containsExactly(localeState.getSourceLocale()); 27 | } 28 | 29 | @Test 30 | public void testAppLocales_nullSourceLocale_nullAppLocales() { 31 | LocaleState localeState = new LocaleState(null, null, 32 | null, 33 | new Locale("el")); 34 | 35 | assertThat(localeState.getAppLocales()).asList().containsExactly(localeState.getSourceLocale()); 36 | } 37 | 38 | @Test 39 | public void testAppLocalesContainSourceLocale_sourceLocaleIncluded() { 40 | LocaleState localeState = new LocaleState(null, "en", 41 | new String[]{"en", "el"}, 42 | new Locale("el")); 43 | 44 | assertThat(localeState.getAppLocales()).asList().contains(localeState.getSourceLocale()); 45 | } 46 | 47 | @Test 48 | public void testAppLocalesContainSourceLocale_sourceLocaleNotIncluded() { 49 | LocaleState localeState = new LocaleState(null, "en", 50 | new String[]{"el"}, 51 | new Locale("el")); 52 | 53 | assertThat(localeState.getAppLocales()).asList().contains(localeState.getSourceLocale()); 54 | } 55 | 56 | @Test 57 | public void testTranslatedLocalesDoNotContainSourceLocale() { 58 | LocaleState localeState = new LocaleState(null, "en", 59 | new String[]{"en", "el"}, 60 | new Locale("el")); 61 | 62 | assertThat(localeState.getTranslatedLocales()).asList().doesNotContain(localeState.getSourceLocale()); 63 | } 64 | 65 | @Test 66 | public void testTranslatedLocalesEmpty_AppLocalesContainOnlySourceLocale() { 67 | LocaleState localeState = new LocaleState(null, "en", 68 | new String[]{"en"}, 69 | new Locale("el")); 70 | 71 | assertThat(localeState.getTranslatedLocales()).isEmpty(); 72 | } 73 | 74 | @Test 75 | public void testTranslatedLocalesEmpty_nullAppLocales() { 76 | LocaleState localeState = new LocaleState(null, "en", 77 | null, 78 | new Locale("el")); 79 | 80 | assertThat(localeState.getTranslatedLocales()).isEmpty(); 81 | } 82 | 83 | @Test 84 | public void testTranslatedLocales_manyAppLocales() { 85 | LocaleState localeState = new LocaleState(null, "en", 86 | new String[]{"en", "el", "es"}, 87 | new Locale("el")); 88 | 89 | assertThat(localeState.getTranslatedLocales()).asList().containsExactly("el", "es"); 90 | } 91 | 92 | @Test 93 | public void testResolvedLocale_exactMatchExists() { 94 | LocaleState localeState = new LocaleState(null, "en", 95 | new String[]{"en", "el_GR", "el_CY", "el", "es"}, 96 | new Locale("el", "gr")); 97 | 98 | assertThat(localeState.getResolvedLocale()).isEqualTo("el_GR"); 99 | } 100 | 101 | @Test 102 | public void testResolvedLocale_parentDialectExists() { 103 | LocaleState localeState = new LocaleState(null, "en", 104 | new String[]{"en", "el_CY", "el", "es"}, 105 | new Locale("el", "gr")); 106 | 107 | assertThat(localeState.getResolvedLocale()).isEqualTo("el"); 108 | } 109 | 110 | @Test 111 | public void testResolvedLocale_childDialectExists() { 112 | LocaleState localeState = new LocaleState(null, "en", 113 | new String[]{"en", "el_CY", "es"}, 114 | new Locale("el", "gr")); 115 | 116 | assertThat(localeState.getResolvedLocale()).isEqualTo("el_CY"); 117 | } 118 | 119 | @Test 120 | public void testResolvedLocale_noMatchExists() { 121 | LocaleState localeState = new LocaleState(null, "en", 122 | new String[]{"en", "es"}, 123 | new Locale("el", "gr")); 124 | 125 | assertThat(localeState.getResolvedLocale()).isNull(); 126 | } 127 | 128 | @Test 129 | public void testIsSourceLocale_same() { 130 | LocaleState localeState = new LocaleState(null, "en", 131 | new String[]{"en", "el"}, 132 | new Locale("en", "US")); 133 | 134 | assertThat(localeState.isSourceLocale()).isTrue(); 135 | } 136 | 137 | @Test 138 | public void testIsSourceLocale_different() { 139 | LocaleState localeState = new LocaleState(null, "en", 140 | new String[]{"en", "el"}, 141 | new Locale("el", "GR")); 142 | 143 | assertThat(localeState.isSourceLocale()).isFalse(); 144 | } 145 | 146 | @Test 147 | public void testIsSourceLocale_resolvedLocaleNull() { 148 | LocaleState localeState = new LocaleState(null, "en", 149 | new String[]{"en", "el"}, 150 | new Locale("es", "ES")); 151 | 152 | assertThat(localeState.isSourceLocale()).isFalse(); 153 | } 154 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/test/java/com/transifex/txnative/cache/TxDecoratorCacheTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import com.transifex.common.LocaleData; 4 | 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.mockito.junit.MockitoJUnit; 8 | import org.mockito.junit.MockitoRule; 9 | 10 | import static org.mockito.Mockito.mock; 11 | import static org.mockito.Mockito.verify; 12 | import static org.mockito.internal.verification.VerificationModeFactory.times; 13 | 14 | public class TxDecoratorCacheTest { 15 | 16 | // In these tests, we check that the internal cache methods are called by TxDecoratorCache as 17 | // expected. 18 | 19 | @Rule 20 | public MockitoRule mockitoRule = MockitoJUnit.rule(); 21 | 22 | @Test 23 | public void testGetAll() { 24 | TxMemoryCache internalCache = mock(TxMemoryCache.class); 25 | 26 | TxDecoratorCache decoratorCache = new TxDecoratorCache(internalCache); 27 | decoratorCache.get(); 28 | 29 | verify(internalCache, times(1)).get(); 30 | } 31 | 32 | @Test 33 | public void testGet() { 34 | TxMemoryCache internalCache = mock(TxMemoryCache.class); 35 | 36 | TxDecoratorCache decoratorCache = new TxDecoratorCache(internalCache); 37 | decoratorCache.get("key1", "el"); 38 | 39 | verify(internalCache, times(1)).get("key1", "el"); 40 | } 41 | 42 | @Test 43 | public void testUpdate() { 44 | TxMemoryCache internalCache = mock(TxMemoryCache.class); 45 | 46 | TxDecoratorCache decoratorCache = new TxDecoratorCache(internalCache); 47 | LocaleData.TranslationMap map = new LocaleData.TranslationMap(0); 48 | decoratorCache.update(map); 49 | 50 | verify(internalCache, times(1)).update(map); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/test/java/com/transifex/txnative/cache/TxDiskTranslationsProviderFilesTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import android.os.Build; 4 | 5 | import com.transifex.common.LocaleData; 6 | import com.transifex.common.TempDirHelper; 7 | import com.transifex.common.TranslationMapStorage; 8 | 9 | import org.junit.After; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.robolectric.RobolectricTestRunner; 14 | import org.robolectric.annotation.Config; 15 | 16 | import static com.google.common.truth.Truth.assertThat; 17 | 18 | @RunWith(RobolectricTestRunner.class) 19 | @Config(sdk = Build.VERSION_CODES.P) 20 | public class TxDiskTranslationsProviderFilesTest { 21 | 22 | // Tests the File version of TxDiskTranslationsProvider. 23 | 24 | TempDirHelper tempDirHelper = null; 25 | 26 | @Before 27 | public void setUp() { 28 | tempDirHelper = new TempDirHelper(); 29 | tempDirHelper.setUp(); 30 | } 31 | 32 | @After 33 | public void Teardown() { 34 | if (tempDirHelper != null) { 35 | tempDirHelper.tearDown(); 36 | } 37 | } 38 | 39 | private LocaleData.TranslationMap getElTranslationMap() { 40 | LocaleData.LocaleStrings elStrings = new LocaleData.LocaleStrings(1); 41 | elStrings.put("tx_test_key", new LocaleData.StringInfo("test ελ tx")); 42 | 43 | LocaleData.TranslationMap translationMap = new LocaleData.TranslationMap(1); 44 | translationMap.put("el", elStrings); 45 | 46 | return translationMap; 47 | } 48 | 49 | @Test 50 | public void testGetTranslations_normal() { 51 | assertThat(tempDirHelper.getFile().mkdirs()).isTrue(); 52 | 53 | LocaleData.TranslationMap map = getElTranslationMap(); 54 | TranslationMapStorage storage = new TranslationMapStorage(TranslationMapStorage.DEFAULT_TRANSLATION_FILENAME); 55 | storage.toDisk(map, tempDirHelper.getFile()); 56 | 57 | TxDiskTranslationsProvider provider = new TxDiskTranslationsProvider(tempDirHelper.getFile()); 58 | 59 | assertThat(provider.getTranslations()).isEqualTo(map); 60 | } 61 | 62 | @Test 63 | public void testGetTranslations_dirDoesNotExist_returnNullTranslationMap() { 64 | TxDiskTranslationsProvider provider = new TxDiskTranslationsProvider(tempDirHelper.getFile()); 65 | 66 | assertThat(provider.getTranslations()).isNull(); 67 | } 68 | 69 | @Test 70 | public void testGetTranslations_emptyDir_returnNullTranslationMap() { 71 | assertThat(tempDirHelper.getFile().mkdirs()).isTrue(); 72 | 73 | TxDiskTranslationsProvider provider = new TxDiskTranslationsProvider(tempDirHelper.getFile()); 74 | 75 | assertThat(provider.getTranslations()).isNull(); 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/test/java/com/transifex/txnative/cache/TxFileOutputCacheDecoratorTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import com.google.common.util.concurrent.MoreExecutors; 4 | import com.transifex.common.LocaleData; 5 | import com.transifex.common.TempDirHelper; 6 | import com.transifex.common.TranslationMapStorage; 7 | 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import java.io.File; 13 | import java.io.FileOutputStream; 14 | import java.io.IOException; 15 | import java.nio.charset.StandardCharsets; 16 | 17 | import static com.google.common.truth.Truth.assertThat; 18 | 19 | public class TxFileOutputCacheDecoratorTest { 20 | 21 | TempDirHelper tempDir = null; 22 | 23 | @Before 24 | public void setUp() { 25 | tempDir = new TempDirHelper(); 26 | tempDir.setUp(); 27 | } 28 | 29 | @After 30 | public void Teardown() { 31 | if (tempDir != null) { 32 | tempDir.tearDown(); 33 | } 34 | } 35 | 36 | private LocaleData.TranslationMap getElTranslationMap() { 37 | LocaleData.LocaleStrings elStrings = new LocaleData.LocaleStrings(1); 38 | elStrings.put("tx_test_key", new LocaleData.StringInfo("test ελ tx")); 39 | 40 | LocaleData.TranslationMap translationMap = new LocaleData.TranslationMap(1); 41 | translationMap.put("el", elStrings); 42 | 43 | return translationMap; 44 | } 45 | 46 | @Test 47 | public void testUpdate_normal_writeTranslations() { 48 | assertThat(tempDir.getFile().mkdirs()).isTrue(); 49 | 50 | TxMemoryCache internalCache = new TxMemoryCache(); 51 | TxFileOutputCacheDecorator fileOutputCache = new TxFileOutputCacheDecorator( 52 | MoreExecutors.directExecutor(), tempDir.getFile(), internalCache); 53 | LocaleData.TranslationMap map = getElTranslationMap(); 54 | fileOutputCache.update(map); 55 | 56 | // Check that internal cache is updated 57 | assertThat(fileOutputCache.get()).isEqualTo(map); 58 | 59 | // Check that the translations on disk are correct 60 | TranslationMapStorage storage = new TranslationMapStorage(TranslationMapStorage.DEFAULT_TRANSLATION_FILENAME); 61 | File elStringFile = new File(tempDir.getFile().getPath() + File.separator + "el" 62 | + File.separator + TranslationMapStorage.DEFAULT_TRANSLATION_FILENAME); 63 | assertThat(elStringFile.exists()).isTrue(); 64 | LocaleData.TranslationMap readMap = storage.fromDisk(tempDir.getFile()); 65 | assertThat(readMap).isEqualTo(map); 66 | } 67 | 68 | @Test 69 | public void testUpdate_translationFileExistsForNonSupportedLocale_fileIsDeleted() { 70 | // Create a pre-existing translation file for de 71 | File localeDir = new File(tempDir.getFile().getAbsoluteFile() + File.separator + "de"); 72 | boolean localeDirCreated = localeDir.mkdirs(); 73 | assertThat(localeDirCreated).isTrue(); 74 | File dummyDeStringFile = new File(localeDir + File.separator + "strings.txt"); 75 | boolean dummyFileWritten = false; 76 | try { 77 | FileOutputStream outputStream = new FileOutputStream(dummyDeStringFile); 78 | String dummyContent = "some text"; 79 | outputStream.write(dummyContent.getBytes(StandardCharsets.UTF_8)); 80 | outputStream.close(); 81 | dummyFileWritten = true; 82 | } catch (IOException ignored) {} 83 | assertThat(dummyFileWritten).isTrue(); 84 | 85 | TxMemoryCache internalCache = new TxMemoryCache(); 86 | TxFileOutputCacheDecorator fileOutputCache = new TxFileOutputCacheDecorator( 87 | MoreExecutors.directExecutor(), tempDir.getFile(), internalCache); 88 | LocaleData.TranslationMap map = getElTranslationMap(); 89 | fileOutputCache.update(map); 90 | 91 | // Check that previous de folder is deleted and new el is created 92 | assertThat(tempDir.getFile().list()).asList().containsExactly("el"); 93 | 94 | // Check that old file doesn't exist 95 | assertThat(dummyDeStringFile.exists()).isFalse(); 96 | 97 | // Check that internal cache is updated 98 | assertThat(fileOutputCache.get()).isEqualTo(map); 99 | 100 | // Check that the translations on disk are correct 101 | TranslationMapStorage storage = new TranslationMapStorage(TranslationMapStorage.DEFAULT_TRANSLATION_FILENAME); 102 | File elStringFile = new File(tempDir.getFile().getPath() + File.separator + "el" 103 | + File.separator + TranslationMapStorage.DEFAULT_TRANSLATION_FILENAME); 104 | assertThat(elStringFile.exists()).isTrue(); 105 | LocaleData.TranslationMap readMap = storage.fromDisk(tempDir.getFile()); 106 | assertThat(readMap).isEqualTo(map); 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/test/java/com/transifex/txnative/cache/TxMemoryCacheTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import com.transifex.common.LocaleData; 4 | 5 | import org.junit.Test; 6 | 7 | import java.util.HashMap; 8 | 9 | import static com.google.common.truth.Truth.assertThat; 10 | 11 | public class TxMemoryCacheTest { 12 | 13 | private static LocaleData.TranslationMap getDummyTranslationMap() { 14 | HashMap dic1 = new HashMap<>(); 15 | dic1.put("key1", new LocaleData.StringInfo("val1")); 16 | LocaleData.LocaleStrings elStrings = new LocaleData.LocaleStrings(dic1); 17 | 18 | HashMap dic2 = new HashMap<>(); 19 | dic2.put("key1", new LocaleData.StringInfo("val1 es")); 20 | LocaleData.LocaleStrings esStrings = new LocaleData.LocaleStrings(dic2); 21 | 22 | LocaleData.TranslationMap translationMap = new LocaleData.TranslationMap(2); 23 | translationMap.put("el", elStrings); 24 | translationMap.put("es", esStrings); 25 | 26 | return translationMap; 27 | } 28 | 29 | private static LocaleData.TranslationMap getDummyTranslationMap2() { 30 | HashMap dic1 = new HashMap<>(); 31 | dic1.put("key1", new LocaleData.StringInfo("val1 de")); 32 | LocaleData.LocaleStrings deStrings = new LocaleData.LocaleStrings(dic1); 33 | 34 | LocaleData.TranslationMap translationMap = new LocaleData.TranslationMap(2); 35 | translationMap.put("de", deStrings); 36 | 37 | return translationMap; 38 | } 39 | 40 | @Test 41 | public void testGet_emptyCache_returnNullString() { 42 | TxMemoryCache cache = new TxMemoryCache(); 43 | 44 | assertThat(cache.get("key1", "el")).isNull(); 45 | } 46 | 47 | @Test 48 | public void testGet_localeNotSupported_returnNullString() { 49 | TxMemoryCache cache = new TxMemoryCache(); 50 | cache.update(getDummyTranslationMap()); 51 | 52 | assertThat(cache.get("key1", "de")).isNull(); 53 | } 54 | 55 | @Test 56 | public void testGet_normal_returnString() { 57 | TxMemoryCache cache = new TxMemoryCache(); 58 | cache.update(getDummyTranslationMap()); 59 | 60 | assertThat(cache.get("key1", "el")).isEqualTo("val1"); 61 | } 62 | 63 | @Test 64 | public void testGet_updateCalledMultipleTimes_returnStringFromLatestUpdate() { 65 | TxMemoryCache cache = new TxMemoryCache(); 66 | cache.update(getDummyTranslationMap()); 67 | 68 | cache.update(getDummyTranslationMap2()); 69 | 70 | assertThat(cache.get("key1", "el")).isNull(); 71 | assertThat(cache.get("key1", "de")).isEqualTo("val1 de"); 72 | } 73 | 74 | @Test 75 | public void testGetAll_normal() { 76 | TxMemoryCache cache = new TxMemoryCache(); 77 | cache.update(getDummyTranslationMap()); 78 | 79 | assertThat(cache.get()).isEqualTo(getDummyTranslationMap()); 80 | } 81 | 82 | @Test 83 | public void testGetAll_emptyCache_returnEmptyMap() { 84 | TxMemoryCache cache = new TxMemoryCache(); 85 | 86 | assertThat(cache.get()).isNotNull(); 87 | assertThat(cache.get().getLocales()).isEmpty(); 88 | } 89 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/test/java/com/transifex/txnative/cache/TxProviderBasedCacheTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import com.transifex.common.LocaleData; 4 | 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.mockito.InOrder; 8 | import org.mockito.junit.MockitoJUnit; 9 | import org.mockito.junit.MockitoRule; 10 | 11 | import static org.mockito.ArgumentMatchers.any; 12 | import static org.mockito.Mockito.inOrder; 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.times; 15 | import static org.mockito.Mockito.verify; 16 | 17 | public class TxProviderBasedCacheTest { 18 | 19 | @Rule 20 | public MockitoRule mockitoRule = MockitoJUnit.rule(); 21 | 22 | private LocaleData.TranslationMap getElTranslationMap1() { 23 | LocaleData.LocaleStrings elStrings = new LocaleData.LocaleStrings(1); 24 | elStrings.put("tx_test_key", new LocaleData.StringInfo("test ελ tx")); 25 | 26 | LocaleData.TranslationMap translationMap = new LocaleData.TranslationMap(1); 27 | translationMap.put("el", elStrings); 28 | 29 | return translationMap; 30 | } 31 | 32 | private LocaleData.TranslationMap getElTranslationMap2() { 33 | LocaleData.LocaleStrings elStrings = new LocaleData.LocaleStrings(1); 34 | elStrings.put("tx_test_key", new LocaleData.StringInfo("test ελ tx 2")); 35 | 36 | LocaleData.TranslationMap translationMap = new LocaleData.TranslationMap(1); 37 | translationMap.put("el", elStrings); 38 | 39 | return translationMap; 40 | } 41 | 42 | @Test 43 | public void testConstructor_twoProviders_callInternalCacheUpdateInSpecificOrder() { 44 | TxTranslationsProvider provider1 = new TxTranslationsProvider() { 45 | 46 | @Override 47 | public LocaleData.TranslationMap getTranslations() { 48 | return getElTranslationMap1(); 49 | } 50 | }; 51 | 52 | TxTranslationsProvider provider2 = new TxTranslationsProvider() { 53 | 54 | @Override 55 | public LocaleData.TranslationMap getTranslations() { 56 | return getElTranslationMap2(); 57 | } 58 | }; 59 | 60 | TxCache internalCache = mock(TxCache.class); 61 | TxTranslationsProvider[] providers = new TxTranslationsProvider[]{provider1, provider2}; 62 | TxProviderBasedCache providerBasedCache = new TxProviderBasedCache(providers, internalCache); 63 | 64 | // We check if the internal cache's update() was called exactly 2 times, passing provider1 65 | // content in the first time and provider2 content the second time. 66 | verify(internalCache, times(2)).update(any(LocaleData.TranslationMap.class)); 67 | InOrder orderVerifier = inOrder(internalCache); 68 | orderVerifier.verify(internalCache, times(1)).update(getElTranslationMap1()); 69 | orderVerifier.verify(internalCache, times(1)).update(getElTranslationMap2()); 70 | orderVerifier.verifyNoMoreInteractions(); 71 | } 72 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/test/java/com/transifex/txnative/cache/TxReadonlyCacheDecoratorTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import com.transifex.common.LocaleData; 4 | 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.mockito.junit.MockitoJUnit; 8 | import org.mockito.junit.MockitoRule; 9 | 10 | import static org.mockito.Mockito.mock; 11 | import static org.mockito.Mockito.times; 12 | import static org.mockito.Mockito.verify; 13 | 14 | public class TxReadonlyCacheDecoratorTest { 15 | 16 | @Rule 17 | public MockitoRule mockitoRule = MockitoJUnit.rule(); 18 | 19 | @Test 20 | public void testUpdate_notCallInternalUpdate() { 21 | TxMemoryCache internalCache = mock(TxMemoryCache.class); 22 | TxReadonlyCacheDecorator readOnlyCache = new TxReadonlyCacheDecorator(internalCache); 23 | 24 | // Make sure that the internal cache's update() was not called after calling the readOnlyCache's 25 | // update 26 | LocaleData.TranslationMap map = new LocaleData.TranslationMap(0); 27 | readOnlyCache.update(map); 28 | verify(internalCache, times(0)).update(map); 29 | } 30 | 31 | @Test 32 | public void testGet_callInternalGet() { 33 | TxMemoryCache internalCache = mock(TxMemoryCache.class); 34 | TxReadonlyCacheDecorator readOnlyCache = new TxReadonlyCacheDecorator(internalCache); 35 | 36 | // Make sure that the internal cache's get() was called after calling the readOnlyCache's 37 | // get() 38 | readOnlyCache.get(); 39 | verify(internalCache, times(1)).get(); 40 | } 41 | 42 | 43 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/test/java/com/transifex/txnative/cache/TxUpdateFilterCacheTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.cache; 2 | 3 | import android.os.Build; 4 | 5 | import com.transifex.common.LocaleData; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.robolectric.RobolectricTestRunner; 10 | import org.robolectric.annotation.Config; 11 | 12 | import static com.google.common.truth.Truth.assertThat; 13 | 14 | // We need roboelectric to emulate TextUtils 15 | @RunWith(RobolectricTestRunner.class) 16 | @Config(sdk = Build.VERSION_CODES.P) 17 | public class TxUpdateFilterCacheTest { 18 | 19 | // The following Translation Maps are based on the table found in TxCacheUpdatePolicy 20 | 21 | private LocaleData.TranslationMap getTranslations1() { 22 | LocaleData.LocaleStrings strings = new LocaleData.LocaleStrings(10); 23 | strings.put("a", new LocaleData.StringInfo("a")); 24 | strings.put("b", new LocaleData.StringInfo("b")); 25 | strings.put("c", new LocaleData.StringInfo("c")); 26 | strings.put("d", new LocaleData.StringInfo("")); 27 | strings.put("e", new LocaleData.StringInfo("")); 28 | 29 | LocaleData.TranslationMap map = new LocaleData.TranslationMap(1); 30 | map.put("el", strings); 31 | 32 | return map; 33 | } 34 | 35 | private LocaleData.TranslationMap getTranslations2() { 36 | LocaleData.LocaleStrings strings = new LocaleData.LocaleStrings(10); 37 | strings.put("b", new LocaleData.StringInfo("B")); 38 | strings.put("c", new LocaleData.StringInfo("")); 39 | strings.put("e", new LocaleData.StringInfo("E")); 40 | strings.put("f", new LocaleData.StringInfo("F")); 41 | strings.put("g", new LocaleData.StringInfo("")); 42 | 43 | LocaleData.TranslationMap map = new LocaleData.TranslationMap(1); 44 | map.put("el", strings); 45 | 46 | return map; 47 | } 48 | 49 | // This is the expected result of applying getTranslations2() on getTranslations1() using 50 | // TxUpdateFilterCache.UPDATE_USING_TRANSLATED 51 | private LocaleData.TranslationMap getTranslationsForUpdateUsingTranslatedGroundTruth() { 52 | LocaleData.LocaleStrings strings = new LocaleData.LocaleStrings(10); 53 | strings.put("a", new LocaleData.StringInfo("a")); 54 | strings.put("b", new LocaleData.StringInfo("B")); 55 | strings.put("c", new LocaleData.StringInfo("c")); 56 | strings.put("d", new LocaleData.StringInfo("")); 57 | strings.put("e", new LocaleData.StringInfo("E")); 58 | strings.put("f", new LocaleData.StringInfo("F")); 59 | 60 | LocaleData.TranslationMap map = new LocaleData.TranslationMap(1); 61 | map.put("el", strings); 62 | 63 | return map; 64 | } 65 | 66 | @Test 67 | public void testUpdate_replaceAllPolicy_getCorrectMap() { 68 | int policy = TxUpdateFilterCache.TxCacheUpdatePolicy.REPLACE_ALL; 69 | TxMemoryCache internalCache = new TxMemoryCache(); 70 | internalCache.update(getTranslations1()); 71 | TxUpdateFilterCache updateFilterCache = new TxUpdateFilterCache(policy, internalCache); 72 | updateFilterCache.update(getTranslations2()); 73 | 74 | assertThat(updateFilterCache.get()).isEqualTo(getTranslations2()); 75 | } 76 | 77 | @Test 78 | public void testUpdate_updateUsingTranslatedPolicy_getCorrectMap() { 79 | int policy = TxUpdateFilterCache.TxCacheUpdatePolicy.UPDATE_USING_TRANSLATED; 80 | TxMemoryCache internalCache = new TxMemoryCache(); 81 | internalCache.update(getTranslations1()); 82 | TxUpdateFilterCache updateFilterCache = new TxUpdateFilterCache(policy, internalCache); 83 | updateFilterCache.update(getTranslations2()); 84 | 85 | assertThat(updateFilterCache.get()).isEqualTo(getTranslationsForUpdateUsingTranslatedGroundTruth()); 86 | } 87 | 88 | @Test 89 | public void testUpdate_replaceAllAndReadOnlyInternalCachePolicy_keepInternalCacheUnchanged() { 90 | // This tests makes sure that TxUpdateFilterCache updates the internal cache by calling 91 | // its update() method and not by accidentally changing the internal cache's map 92 | 93 | int policy = TxUpdateFilterCache.TxCacheUpdatePolicy.REPLACE_ALL; 94 | TxMemoryCache internalCache = new TxMemoryCache(); 95 | internalCache.update(getTranslations1()); 96 | TxUpdateFilterCache updateFilterCache = new TxUpdateFilterCache(policy, new TxReadonlyCacheDecorator(internalCache)); 97 | updateFilterCache.update(getTranslations2()); 98 | 99 | assertThat(updateFilterCache.get()).isEqualTo(getTranslations1()); 100 | } 101 | 102 | @Test 103 | public void testUpdate_updateUsingTranslatedPolicyAndReadOnlyInternalCache_keepInternalCacheUnchanged() { 104 | // This tests makes sure that TxUpdateFilterCache updates the internal cache by calling 105 | // its update() method and not by accidentally changing the internal cache's map 106 | 107 | int policy = TxUpdateFilterCache.TxCacheUpdatePolicy.UPDATE_USING_TRANSLATED; 108 | TxMemoryCache internalCache = new TxMemoryCache(); 109 | internalCache.update(getTranslations1()); 110 | TxUpdateFilterCache updateFilterCache = new TxUpdateFilterCache(policy, new TxReadonlyCacheDecorator(internalCache)); 111 | updateFilterCache.update(getTranslations2()); 112 | 113 | assertThat(updateFilterCache.get()).isEqualTo(getTranslations1()); 114 | } 115 | 116 | @Test 117 | public void testUpdate_updateUsingTranslatedPolicyAndEmptyInternalCache_addNewLocale() { 118 | // We make sure that a new locale can be added when UPDATE_USING_TRANSLATED is used. 119 | 120 | int policy = TxUpdateFilterCache.TxCacheUpdatePolicy.UPDATE_USING_TRANSLATED; 121 | TxMemoryCache internalCache = new TxMemoryCache(); 122 | TxUpdateFilterCache updateFilterCache = new TxUpdateFilterCache(policy, internalCache); 123 | updateFilterCache.update(getTranslations1()); 124 | 125 | assertThat(updateFilterCache.get().getLocales()).containsExactly("el"); 126 | assertThat(updateFilterCache.get("a", "el")).isEqualTo("a"); 127 | assertThat(updateFilterCache.get("d", "el")).isNull(); 128 | } 129 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/test/java/com/transifex/txnative/missingpolicy/AndroidMissingPolicyTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.missingpolicy; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | 6 | import com.transifex.txnative.test.R; 7 | 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.robolectric.RobolectricTestRunner; 12 | import org.robolectric.annotation.Config; 13 | 14 | import androidx.test.core.app.ApplicationProvider; 15 | 16 | import static com.google.common.truth.Truth.assertThat; 17 | 18 | @RunWith(RobolectricTestRunner.class) 19 | @Config(sdk = Build.VERSION_CODES.P) 20 | public class AndroidMissingPolicyTest { 21 | 22 | // The tests rely on the following directories: 23 | // 24 | // test/res/values 25 | // test/res/values-el 26 | // test/res/values-es 27 | 28 | private Context mockContext; 29 | 30 | @Before 31 | public void setUp() { 32 | // inject context provided by Robolectric 33 | mockContext = ApplicationProvider.getApplicationContext(); 34 | } 35 | 36 | @Test 37 | @Config(qualifiers = "el-rGR") 38 | public void testGet() { 39 | // We test if AndroidMissingPolicy can return the el translation found in the app's 40 | // test/res/values-el/strings.xml 41 | 42 | CharSequence sourceString = "dummy source string"; 43 | String resourceEntryName = mockContext.getResources().getResourceEntryName(R.string.tx_test_key); 44 | 45 | AndroidMissingPolicy policy = new AndroidMissingPolicy(); 46 | CharSequence translated = policy.get(mockContext.getResources(), sourceString, R.string.tx_test_key, resourceEntryName, "el"); 47 | 48 | assertThat(translated).isEqualTo("test ελ"); 49 | } 50 | 51 | @Test 52 | @Config(qualifiers = "el-rGR") 53 | public void testGetQuantityString() { 54 | // We test if AndroidMissingPolicy can return the el translation found in the app's 55 | // test/res/values-el/strings.xml 56 | 57 | CharSequence sourceString = "dummy source string"; 58 | String resourceEntryName = mockContext.getResources().getResourceEntryName(R.plurals.tx_plural_test_key); 59 | 60 | AndroidMissingPolicy policy = new AndroidMissingPolicy(); 61 | CharSequence translated = policy.getQuantityString(mockContext.getResources(), sourceString, R.plurals.tx_plural_test_key, 1, resourceEntryName, "el"); 62 | 63 | assertThat(translated).isEqualTo("αυτοκίνητο"); 64 | } 65 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/test/java/com/transifex/txnative/missingpolicy/PseudoTranslationPolicyTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.missingpolicy; 2 | 3 | import android.content.res.Resources; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | import static com.google.common.truth.Truth.assertThat; 9 | import static org.mockito.Mockito.mock; 10 | 11 | public class PseudoTranslationPolicyTest { 12 | 13 | final int stringId = 0; 14 | final String stringResourceName = "dummy_name"; 15 | final String locale = "el"; 16 | 17 | private Resources resources; 18 | 19 | @Before 20 | public void setUp() { 21 | resources = mock(Resources.class); 22 | } 23 | 24 | @Test 25 | public void testProcessString() { 26 | String sourceString = "The quick\n brown fox \nένα!"; 27 | PseudoTranslationPolicy policy = new PseudoTranslationPolicy(); 28 | CharSequence translated = policy.processString(sourceString); 29 | 30 | assertThat(translated).isEqualTo("Ťȟê ʠüıċǩ\n ƀȓøẁñ ƒøẋ \nένα!"); 31 | } 32 | 33 | @Test 34 | public void testProcessString_formatSpecifiers_notAffected() { 35 | // Make sure that format specifiers are not affected 36 | String sourceString = "This is a %s %B test"; 37 | PseudoTranslationPolicy policy = new PseudoTranslationPolicy(); 38 | CharSequence translated = policy.processString(sourceString); 39 | 40 | assertThat(translated).isEqualTo("Ťȟıš ıš à %s %B ťêšť"); 41 | } 42 | 43 | @Test 44 | public void testProcessString_formatSpecifiers2_notAffected() { 45 | String sourceString = "This is a %32.12f test"; 46 | PseudoTranslationPolicy policy = new PseudoTranslationPolicy(); 47 | CharSequence translated = policy.processString(sourceString); 48 | 49 | assertThat(translated).isEqualTo("Ťȟıš ıš à %32.12f ťêšť"); 50 | } 51 | 52 | @Test 53 | public void testProcessString_formatSpecifiers3_notAffected() { 54 | String sourceString = "This is a |%010d| test"; 55 | PseudoTranslationPolicy policy = new PseudoTranslationPolicy(); 56 | CharSequence translated = policy.processString(sourceString); 57 | 58 | assertThat(translated).isEqualTo("Ťȟıš ıš à |%010d| ťêšť"); 59 | } 60 | 61 | @Test 62 | public void testProcessString_formatSpecifierWithDate_notAffected() { 63 | String sourceString = "This is a %tM test"; 64 | PseudoTranslationPolicy policy = new PseudoTranslationPolicy(); 65 | CharSequence translated = policy.processString(sourceString); 66 | 67 | assertThat(translated).isEqualTo("Ťȟıš ıš à %tM ťêšť"); 68 | } 69 | 70 | @Test 71 | public void testGet() { 72 | String sourceString = "The quick\n brown fox \nένα!"; 73 | PseudoTranslationPolicy policy = new PseudoTranslationPolicy(); 74 | CharSequence translated = policy.get(resources, sourceString, stringId, stringResourceName, locale); 75 | 76 | assertThat(translated).isEqualTo("Ťȟê ʠüıċǩ\n ƀȓøẁñ ƒøẋ \nένα!"); 77 | } 78 | 79 | @Test 80 | public void testGetQuantityString() { 81 | String sourceString = "The quick\n brown fox \nένα!"; 82 | PseudoTranslationPolicy policy = new PseudoTranslationPolicy(); 83 | CharSequence translated = policy.getQuantityString(resources, sourceString, stringId, 1, stringResourceName, locale); 84 | 85 | assertThat(translated).isEqualTo("Ťȟê ʠüıċǩ\n ƀȓøẁñ ƒøẋ \nένα!"); 86 | } 87 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/test/java/com/transifex/txnative/missingpolicy/SourceStringPolicyTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.missingpolicy; 2 | 3 | import android.content.res.Resources; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | import static com.google.common.truth.Truth.assertThat; 9 | import static org.mockito.Mockito.mock; 10 | 11 | public class SourceStringPolicyTest { 12 | 13 | final int stringId = 0; 14 | final String stringResourceName = "dummy_name"; 15 | final String locale = "el"; 16 | private Resources resources; 17 | 18 | @Before 19 | public void setUp() { 20 | resources = mock(Resources.class); 21 | } 22 | 23 | @Test 24 | public void testGet() { 25 | SourceStringPolicy sourceStringPolicy = new SourceStringPolicy(); 26 | 27 | assertThat(sourceStringPolicy.get(resources, "test", stringId, stringResourceName, locale)) 28 | .isEqualTo("test"); 29 | } 30 | 31 | @Test 32 | public void testGetQuantityString() { 33 | SourceStringPolicy sourceStringPolicy = new SourceStringPolicy(); 34 | 35 | assertThat(sourceStringPolicy.getQuantityString(resources, "test", stringId, 1, stringResourceName, locale)) 36 | .isEqualTo("test"); 37 | } 38 | } -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/test/java/com/transifex/txnative/missingpolicy/WrappedStringPolicyTest.java: -------------------------------------------------------------------------------- 1 | package com.transifex.txnative.missingpolicy; 2 | 3 | import android.content.res.Resources; 4 | import android.os.Build; 5 | import android.text.Spanned; 6 | import android.text.SpannedString; 7 | import android.text.style.StyleSpan; 8 | 9 | import com.transifex.txnative.Utils; 10 | 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.robolectric.RobolectricTestRunner; 15 | import org.robolectric.annotation.Config; 16 | 17 | import static android.text.Html.FROM_HTML_MODE_LEGACY; 18 | import static com.google.common.truth.Truth.assertThat; 19 | import static org.mockito.Mockito.mock; 20 | 21 | @RunWith(RobolectricTestRunner.class) 22 | @Config(sdk = Build.VERSION_CODES.P) 23 | public class WrappedStringPolicyTest { 24 | 25 | final int stringId = 0; 26 | final String stringResourceName = "dummy_name"; 27 | final String locale = "el"; 28 | 29 | private Resources resources; 30 | 31 | @Before 32 | public void setUp() { 33 | resources = mock(Resources.class); 34 | } 35 | 36 | @Test 37 | public void testWrapString_normal() { 38 | WrappedStringPolicy policy = new WrappedStringPolicy("<", " !end"); 39 | String sourceString = "The quick\n brown fox"; 40 | CharSequence translated = policy.wrapString(sourceString); 41 | 42 | assertThat(translated).isEqualTo("brown fox", FROM_HTML_MODE_LEGACY); 87 | CharSequence translated = policy.wrapString(sourceString); 88 | 89 | assertThat(translated).isInstanceOf(Spanned.class); 90 | Spanned spanned = (Spanned) translated; 91 | StyleSpan[] spans = spanned.getSpans(0, spanned.length(), StyleSpan.class); 92 | StyleSpan span = spans[0]; 93 | int start = spanned.getSpanStart(span); 94 | int end = spanned.getSpanEnd(span); 95 | CharSequence styledPart = spanned.subSequence(start, end); 96 | 97 | assertThat(styledPart).isNotNull(); 98 | assertThat(styledPart.toString()).isEqualTo("brown"); 99 | 100 | assertThat(translated.toString()).isEqualTo("start! The quick brown fox !end"); 101 | } 102 | 103 | @Test 104 | public void testGet_normal() { 105 | WrappedStringPolicy policy = new WrappedStringPolicy("<", " !end"); 106 | String sourceString = "The quick\n brown fox"; 107 | CharSequence translated = policy.get(resources, sourceString, stringId, stringResourceName, locale); 108 | 109 | assertThat(translated).isEqualTo(" 2 | 3 | test ελ 4 | 5 | αυτοκίνητα 6 | αυτοκίνητο 7 | 8 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/test/res/values-es/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | test es 4 | -------------------------------------------------------------------------------- /TransifexNativeSDK/txsdk/src/test/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | test 4 | 5 | cars 6 | car 7 | 8 | car 2 9 | 10 | --------------------------------------------------------------------------------