From beb8729a63f899930d23c95c31001e0031304f17 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Wed, 14 Aug 2024 11:49:40 +0200 Subject: [PATCH] feat(per-app-language): add Android config plugin --- .../.eslintrc.js | 2 + .../CHANGELOG.md | 8 +++ .../README.md | 41 ++++++++++++ .../app.plugin.js | 1 + .../jest.config.js | 1 + .../package.json | 41 ++++++++++++ .../withAndroidPerAppLanguage.test.ts.snap | 8 +++ .../withAndroidPerAppLanguage.test.ts | 14 ++++ .../src/withAndroidPerAppLanguage.ts | 67 +++++++++++++++++++ .../tsconfig.json | 9 +++ 10 files changed, 192 insertions(+) create mode 100644 packages/react-native-android-per-app-language/.eslintrc.js create mode 100644 packages/react-native-android-per-app-language/CHANGELOG.md create mode 100644 packages/react-native-android-per-app-language/README.md create mode 100644 packages/react-native-android-per-app-language/app.plugin.js create mode 100644 packages/react-native-android-per-app-language/jest.config.js create mode 100644 packages/react-native-android-per-app-language/package.json create mode 100644 packages/react-native-android-per-app-language/src/__tests__/__snapshots__/withAndroidPerAppLanguage.test.ts.snap create mode 100644 packages/react-native-android-per-app-language/src/__tests__/withAndroidPerAppLanguage.test.ts create mode 100644 packages/react-native-android-per-app-language/src/withAndroidPerAppLanguage.ts create mode 100644 packages/react-native-android-per-app-language/tsconfig.json diff --git a/packages/react-native-android-per-app-language/.eslintrc.js b/packages/react-native-android-per-app-language/.eslintrc.js new file mode 100644 index 00000000..27201978 --- /dev/null +++ b/packages/react-native-android-per-app-language/.eslintrc.js @@ -0,0 +1,2 @@ +// @generated by expo-module-scripts +module.exports = require('expo-module-scripts/eslintrc.base.js'); diff --git a/packages/react-native-android-per-app-language/CHANGELOG.md b/packages/react-native-android-per-app-language/CHANGELOG.md new file mode 100644 index 00000000..b02afd0f --- /dev/null +++ b/packages/react-native-android-per-app-language/CHANGELOG.md @@ -0,0 +1,8 @@ +### @config-plugins/react-native-android-per-app-language 1.0.0 + +- +### Added +- Initial release of the `@config-plugins/react-native-android-per-app-language` plugin. +- Implemented support for Android's per-app language preferences feature. +- Added functionality to modify the Android Manifest to include the `localeConfig` attribute. +- Created a config plugin to generate the `locales_config.xml` file with user-specified supported languages. diff --git a/packages/react-native-android-per-app-language/README.md b/packages/react-native-android-per-app-language/README.md new file mode 100644 index 00000000..8d72e4d4 --- /dev/null +++ b/packages/react-native-android-per-app-language/README.md @@ -0,0 +1,41 @@ +# @config-plugins/react-native-android-per-app-language + +### How it works +This plugin automates the manual setup process described in the [Android documentation](https://developer.android.com/guide/topics/resources/app-languages#use-localeconfig) for implementing per-app language preferences: + +1. Modifies the `AndroidManifest.xml` to add the `android:localeConfig` attribute to the `` tag. +2. Creates a `res/xml/locales_config.xml` file with the specified supported languages. + +## Expo installation +To use this plugin in your Expo project, follow these steps: + +1. Install the plugin: + ``` + expo install @config-plugins/react-native-android-per-app-language + ``` + +2. Add the plugin to your `app.json` or `app.config.js`: + ```json + { + "expo": { + "plugins": [ + [ + "@config-plugins/react-native-android-per-app-language", + { + "supportedLanguages": ["en", "es", "fr"] + } + ] + ] + } + } + ``` + + The `supportedLanguages` parameter is required and should be an array of language codes that your app supports. + +3. Rebuild your app: + ``` + expo prebuild + ``` + +This will configure your Android project to support per-app language preferences for the specified languages. + diff --git a/packages/react-native-android-per-app-language/app.plugin.js b/packages/react-native-android-per-app-language/app.plugin.js new file mode 100644 index 00000000..c5d539dd --- /dev/null +++ b/packages/react-native-android-per-app-language/app.plugin.js @@ -0,0 +1 @@ +module.exports = require("./build/withAndroidPerAppLanguage"); diff --git a/packages/react-native-android-per-app-language/jest.config.js b/packages/react-native-android-per-app-language/jest.config.js new file mode 100644 index 00000000..e539b3b1 --- /dev/null +++ b/packages/react-native-android-per-app-language/jest.config.js @@ -0,0 +1 @@ +module.exports = require('expo-module-scripts/jest-preset-plugin'); diff --git a/packages/react-native-android-per-app-language/package.json b/packages/react-native-android-per-app-language/package.json new file mode 100644 index 00000000..a1cef79c --- /dev/null +++ b/packages/react-native-android-per-app-language/package.json @@ -0,0 +1,41 @@ +{ + "name": "@config-plugins/react-native-android-per-app-language", + "version": "8.0.0", + "description": "Config plugin to auto configure react-native-android-per-app-language on prebuild", + "main": "build/withAndroidPerAppLanguage.js", + "types": "build/withAndroidPerAppLanguage.d.ts", + "sideEffects": false, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/expo/config-plugins.git", + "directory": "packages/react-native-android-per-app-language" + }, + "files": [ + "build", + "app.plugin.js" + ], + "scripts": { + "build": "expo-module build", + "clean": "expo-module clean", + "lint": "expo-module lint", + "test": "expo-module test", + "prepare": "expo-module prepare", + "prepublishOnly": "expo-module prepublishOnly", + "expo-module": "expo-module" + }, + "keywords": [ + "android", + "localization", + "react-native-android-per-app-language", + "react-native", + "expo" + ], + "peerDependencies": { + "expo": "^51" + }, + "devDependencies": { + "expo-module-scripts": "^3.5.1" + }, + "upstreamPackage": "react-native-android-per-app-language" +} diff --git a/packages/react-native-android-per-app-language/src/__tests__/__snapshots__/withAndroidPerAppLanguage.test.ts.snap b/packages/react-native-android-per-app-language/src/__tests__/__snapshots__/withAndroidPerAppLanguage.test.ts.snap new file mode 100644 index 00000000..3b832e5f --- /dev/null +++ b/packages/react-native-android-per-app-language/src/__tests__/__snapshots__/withAndroidPerAppLanguage.test.ts.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getTemplateFile returns only base-config when subdomains is '*' 1`] = ` +" + + +" +`; diff --git a/packages/react-native-android-per-app-language/src/__tests__/withAndroidPerAppLanguage.test.ts b/packages/react-native-android-per-app-language/src/__tests__/withAndroidPerAppLanguage.test.ts new file mode 100644 index 00000000..36d64a6a --- /dev/null +++ b/packages/react-native-android-per-app-language/src/__tests__/withAndroidPerAppLanguage.test.ts @@ -0,0 +1,14 @@ +import { getTemplateFile } from "../withAndroidPerAppLanguage"; + +describe("getTemplateFile", () => { + it("returns the expected XML content", () => { + const supportedLanguages = ["en", "fr"]; + const expectedContent = ` + + + +`; + const content = getTemplateFile(supportedLanguages); + expect(content).toEqual(expectedContent); + }); +}); diff --git a/packages/react-native-android-per-app-language/src/withAndroidPerAppLanguage.ts b/packages/react-native-android-per-app-language/src/withAndroidPerAppLanguage.ts new file mode 100644 index 00000000..e7b06a5c --- /dev/null +++ b/packages/react-native-android-per-app-language/src/withAndroidPerAppLanguage.ts @@ -0,0 +1,67 @@ +import { + ConfigPlugin, + withAndroidManifest, + AndroidManifest, + withDangerousMod, + AndroidConfig, +} from "expo/config-plugins"; +import * as fs from "fs"; +import * as path from "path"; + +type SupportedLanguages = string[]; + +export const withAndroidPerAppLanguage: ConfigPlugin<{ + supportedLanguages: SupportedLanguages; +}> = (config, { supportedLanguages }) => { + if (!supportedLanguages || !supportedLanguages.length) { + return config; + } + + /** + * Modify the Android Manifest 'application' to include the localeConfig attribute + */ + config = withAndroidManifest(config, (config) => { + config.modResults = addLocaleConfigToManifest(config.modResults); + return config; + }); + + /** + * Create `network_security_config.xml` resource file. + */ + return withDangerousMod(config, [ + "android", + async (config) => { + const folder = path.join( + config.modRequest.platformProjectRoot, + `app/src/main/res/xml`, + ); + fs.mkdirSync(folder, { recursive: true }); + fs.writeFileSync( + path.join(folder, "locales_config.xml"), + getTemplateFile(supportedLanguages), + { encoding: "utf8" }, + ); + return config; + }, + ]); +}; + +export const addLocaleConfigToManifest = (androidManifest: AndroidManifest) => { + const application = + AndroidConfig.Manifest.getMainApplicationOrThrow(androidManifest); + + application.$["android:localeConfig"] = "@xml/locales_config"; + return androidManifest; +}; + +export const getTemplateFile = (supportedLanguages: SupportedLanguages) => { + const localesList = supportedLanguages + .map((lang) => ` `) + .join("\n"); + + const content = ` + +${localesList} +`; + return content; +}; diff --git a/packages/react-native-android-per-app-language/tsconfig.json b/packages/react-native-android-per-app-language/tsconfig.json new file mode 100644 index 00000000..354bddb4 --- /dev/null +++ b/packages/react-native-android-per-app-language/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "expo-module-scripts/tsconfig.plugin", + "compilerOptions": { + "outDir": "build", + "rootDir": "src" + }, + "include": ["./src"], + "exclude": ["**/__mocks__/*", "**/__tests__/*"] +}