diff --git a/.gitignore b/.gitignore index 81f695d..702b9c3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ node_modules package-lock.json jsconfig.json dist -tsconfig.json +/tsconfig.json test/fixture diff --git a/README.md b/README.md index 5f86745..54dba5e 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This will also create fresh `/android` and `/ios` native folders and generate a ```sh npm init --yes now numic ./my-app # Basic default template with tests. -npm init --yea now numic ./my-starter-app app # Tempalte with navigation, data, responsive and styles. +npm init --yes now numic ./my-starter-app app # Tempalte with navigation, data, responsive and styles. ``` This will prompt for an app name that can only contain **alphanumeric** characters and will be used as the initial bundle identifier. Using `NumicApp` as the name will result in `com.numicapp` as the bundle identifier. The name as well as the display name can later be configured in `app.json`. diff --git a/package.json b/package.json index 78575ad..ed4ec26 100644 --- a/package.json +++ b/package.json @@ -46,12 +46,12 @@ "skip-local-postinstall": "^2.0.4" }, "devDependencies": { - "@types/command-exists": "^1.2.1", - "@types/prompts": "^2.4.6", - "@types/semver": "^7.5.3", + "@types/command-exists": "^1.2.2", + "@types/prompts": "^2.4.7", + "@types/semver": "^7.5.4", "jest-fixture": "^4.1.0", "padua": "^2.0.6", - "react-native": "^0.72.5", + "react-native": "^0.72.6", "vitest": "^0.34.6" }, "peerDependencies": { @@ -64,8 +64,8 @@ ".": "./dist/index.js", "./package.json": "./package.json" }, - "bin": "dist/index.js", - "types": "dist/index.d.ts", + "bin": "./dist/index.js", + "types": "./dist/index.d.ts", "files": [ "dist", "configuration/*.json" diff --git a/script/ios.ts b/script/ios.ts index 183a7d2..975591a 100644 --- a/script/ios.ts +++ b/script/ios.ts @@ -40,13 +40,13 @@ export const ios = async (inputs: RunInputs) => { if (!checkCommandVersion('gem -v', '3.4.0')) { log( - 'The "gem" executable version is outdated, make sure to update soon, by running "gem update --system"' + 'The "gem" executable version is outdated, make sure to update soon, by running "gem update --system"', ) } if (!checkCommandVersion('pod --version', '1.12.0')) { log( - 'The "pod" (cocoapods) executable version is outdated, make sure to update soon, by running "gem update"' + 'The "pod" (cocoapods) executable version is outdated, make sure to update soon, by running "gem update"', ) } @@ -55,7 +55,7 @@ export const ios = async (inputs: RunInputs) => { try { execSync('pod update', { cwd: join(basePath(), 'ios'), encoding: 'utf8', stdio: 'pipe' }) } catch (error) { - log('Failed to run "pod updated" in /ios', 'warning') + log('Failed to run "pod update" in /ios', 'warning') console.log(error.stdout) } } else { diff --git a/script/prompt.ts b/script/prompt.ts index 38eaae5..386fc51 100644 --- a/script/prompt.ts +++ b/script/prompt.ts @@ -56,7 +56,7 @@ const getIOSSimulators = () => { simctlOutput = JSON.parse( execSync('xcrun simctl list --json devices available', { encoding: 'utf8', - }).trim() + }).trim(), ).devices } catch (_) { log('Failed to get a list of available iOS simulators', 'error') @@ -66,7 +66,7 @@ const getIOSSimulators = () => { Object.keys(simctlOutput).forEach((runtime) => { allDevices.push( - ...simctlOutput[runtime].map((device) => ({ name: device.name, state: device.state })) + ...simctlOutput[runtime].map((device) => ({ name: device.name, state: device.state })), ) }) @@ -199,7 +199,7 @@ export const prompt = async () => { if (!commandExists('adb')) { log( 'adb command required, install the Android SDK and make sure to add binaries to the PATH variable', - 'error' + 'error', ) } @@ -234,7 +234,7 @@ export const prompt = async () => { if (!commandExists('ios-deploy')) { log( 'ios-deploy required to run on device, install with "sudo npm install -g ios-deploy" or "brew install ios-deploy"', - 'error' + 'error', ) } @@ -253,7 +253,7 @@ export const prompt = async () => { ).device } - ios({ location, mode, device }) + ios({ location, mode, device, simulator }) } if (script === 'android') { diff --git a/template/app/data/Data.ts b/template/app/data/Data.ts index e483b3b..535ea44 100644 --- a/template/app/data/Data.ts +++ b/template/app/data/Data.ts @@ -1,8 +1,8 @@ import { makeAutoObservable } from 'mobx' -import { Language } from '../types' +import { Language } from 'epic-language/native' export const Data = new (class { - language = Language.English + language = Language.en constructor() { makeAutoObservable(this, {}, { autoBind: true }) diff --git a/template/app/package.json b/template/app/package.json index 20906d7..94fd2b7 100644 --- a/template/app/package.json +++ b/template/app/package.json @@ -14,6 +14,7 @@ } }, "dependencies": { + "epic-language": "^0.3.0", "mobx": "^6.10.2", "mobx-react-lite": "^4.0.5", "react": "^18.2.0", @@ -41,12 +42,19 @@ "react-test-renderer": "^18.2.0", "typescript": "^5.2.2" }, + "overrides": { + "chalk": "^4.1.2" + }, "type": "module", "prettier": "./node_modules/numic/configuration/.prettierrc.json", "eslintConfig": { "extends": "./node_modules/numic/configuration/.eslintrc.json" }, - "metro": {}, + "metro": { + "resolver": { + "unstable_enablePackageExports": true + } + }, "jest": { "moduleFileExtensions": [ "ts", @@ -62,10 +70,11 @@ }, "preset": "react-native", "setupFilesAfterEnv": [ - "@testing-library/jest-native/extend-expect" + "@testing-library/jest-native/extend-expect", + "./test/native.mock.ts" ], "transformIgnorePatterns": [ - "node_modules/(?!react-native|@react-native|responsive-react-native|reactigation)" + "node_modules/(?!react-native|@react-native|responsive-react-native|reactigation|epic-language|logua|chalk)" ] } } diff --git a/template/app/screen/Settings.tsx b/template/app/screen/Settings.tsx index ca7f245..9bfc4d4 100644 --- a/template/app/screen/Settings.tsx +++ b/template/app/screen/Settings.tsx @@ -2,14 +2,15 @@ import React from 'react' import { GestureResponderEvent, Text, View, Pressable } from 'react-native' import { back } from 'reactigation' import { createStyles } from 'responsive-react-native' +import { Language, readableLanguage } from 'epic-language/native' import { Screen } from '../markup/Screen' import { Button } from '../markup/Button' import { Header } from '../markup/Header' import { Label } from '../label' import { observer } from 'mobx-react-lite' import { Data } from '../data/Data' -import { Language } from '../types' import { Color, Font, Space } from '../style' +import { translate } from '../translation' const styles = createStyles({ row: { @@ -49,31 +50,31 @@ const LanguageOption = ({ export const Settings = observer(() => { return ( -
+
- Language + {translate('settingsLanguage', undefined, Data.language)} Data.setLanguage(Language.English)} + name={readableLanguage[Language.en]} + active={Data.language === Language.en} + onPress={() => Data.setLanguage(Language.en)} /> Data.setLanguage(Language.Spanish)} + name={readableLanguage[Language.es]} + active={Data.language === Language.es} + onPress={() => Data.setLanguage(Language.es)} /> Data.setLanguage(Language.Chinese)} + name={readableLanguage[Language.zh]} + active={Data.language === Language.zh} + onPress={() => Data.setLanguage(Language.zh)} /> diff --git a/template/app/test/app.test.tsx b/template/app/test/app.test.tsx index 44a73ca..e5d2fc6 100644 --- a/template/app/test/app.test.tsx +++ b/template/app/test/app.test.tsx @@ -4,14 +4,21 @@ import { render, fireEvent } from '@testing-library/react-native' import '@testing-library/jest-native/extend-expect' import { App } from '../App' import { Label } from '../label' +import { Language, readableLanguage } from 'epic-language' test('App renders without crashing.', async () => { const app = render() expect(app).toBeDefined() let title = app.getByLabelText(Label.screenTitle) expect(title).toHaveTextContent('Overview') + // Navigate to settings screen. const settingsButton = app.getByLabelText(Label.openSettingsButton) fireEvent.press(settingsButton) title = app.getByLabelText(Label.screenTitle) expect(title).toHaveTextContent('Settings') + // Switch language. + const spanishButton = app.getByText(readableLanguage[Language.es]) + fireEvent.press(spanishButton) + title = app.getByLabelText(Label.screenTitle) + expect(title).toHaveTextContent('Ajustes') }) diff --git a/template/app/test/native.mock.ts b/template/app/test/native.mock.ts new file mode 100644 index 0000000..ea49380 --- /dev/null +++ b/template/app/test/native.mock.ts @@ -0,0 +1,8 @@ +import { NativeModules } from 'react-native' + +// Mock locale depending on native language by os. +NativeModules.SettingsManager = { + settings: { + AppleLocale: 'en_US', + }, +} diff --git a/template/app/translation.ts b/template/app/translation.ts new file mode 100644 index 0000000..d1395fb --- /dev/null +++ b/template/app/translation.ts @@ -0,0 +1,22 @@ +import { create, Language } from 'epic-language/native' + +export const { translate, Text } = create( + { + settingsTitle: 'Settings', + settingsLanguage: 'Language', + settingsBack: 'Back', + }, + { + [Language.es]: { + settingsTitle: 'Ajustes', + settingsLanguage: 'Idioma', + settingsBack: 'Atrás', + }, + [Language.zh]: { + settingsTitle: '设置', + settingsLanguage: '语言', + settingsBack: '后退', + }, + }, + Language.en +) diff --git a/template/app/tsconfig.json b/template/app/tsconfig.json new file mode 100644 index 0000000..f409bab --- /dev/null +++ b/template/app/tsconfig.json @@ -0,0 +1,8 @@ +{ +"extends": "@tsconfig/react-native/tsconfig.json", +"compilerOptions": { + "module": "NodeNext", + "moduleResolution": "Bundler" +} +} + \ No newline at end of file diff --git a/template/app/types.ts b/template/app/types.ts deleted file mode 100644 index 4e9cec6..0000000 --- a/template/app/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum Language { - English, - Spanish, - Chinese, -}