diff --git a/.github/workflows/translationDryRun.yml b/.github/workflows/translationDryRun.yml deleted file mode 100644 index f0a2ecf41a16..000000000000 --- a/.github/workflows/translationDryRun.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Translation Dry Run - -on: - pull_request: - types: [opened, synchronize] - paths-ignore: ['docs', 'help'] - -jobs: - # We always run generateTranslations --dry-run in CI to verify the script still works end-to-end. - # It imports from @src and @libs; Bun runs it with stubReactNative preload for modules that expect RN. - dryRun: - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - name: Checkout - uses: useblacksmith/checkout@1c9394c220d293645707b625ba9d79685f093a8f # v1 - - - name: Setup Node - uses: ./.github/actions/composite/setupNode - - - name: Capture pre-run checksum - id: before - run: echo "sha=$(sha256sum src/languages/it.ts | cut -d ' ' -f1)" >> "$GITHUB_OUTPUT" - - - name: Run generateTranslations dry run - run: npx bun ./scripts/generateTranslations.ts --dry-run --locales it - - - name: Verify translation output - run: | - # The [it] prefix only ever comes from the dry-run's dummy translator, so a checksum - # change plus this marker proves the script actually re-ran and wrote fresh output, - # not that it silently no-op'd and left the already-committed file untouched. - test "$(sha256sum src/languages/it.ts | cut -d ' ' -f1)" != "${{ steps.before.outputs.sha }}" - grep -q 'This file was automatically generated' src/languages/it.ts - grep -q '\[it\]' src/languages/it.ts - - - name: Explain failure - if: failure() - run: | - echo '::error:: generateTranslations --dry-run failed or did not produce expected output in src/languages/it.ts. Check script imports and recent changes to @src/@libs code the script depends on.' - exit 1 diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 90cdf1780ecb..cee1d7df04c3 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -5,7 +5,7 @@ on: pull_request: types: [opened, synchronize] branches-ignore: [staging, production] - paths: ['**.js', '**.ts', '**.tsx', 'package.json', 'package-lock.json', '**/tsconfig.json'] + paths: ['**.js', '**.ts', '**.tsx', 'package.json', 'package-lock.json', '**/tsconfig*.json'] concurrency: group: ${{ github.ref == 'refs/heads/main' && format('{0}-{1}', github.ref, github.sha) || github.ref }}-typecheck diff --git a/config/eslint/eslint.config.mjs b/config/eslint/eslint.config.mjs index 28c9c5988fdb..859d4e2f38e9 100644 --- a/config/eslint/eslint.config.mjs +++ b/config/eslint/eslint.config.mjs @@ -715,46 +715,20 @@ const config = defineConfig([ }, { - files: ['server/**/*.ts', 'server/**/*.tsx'], + files: ['scripts/**/*.ts', 'tests/tooling/**/*.ts', 'server/{libs,plugins,stubs}/**/*.{ts,tsx}', 'evals/**/*.ts'], languageOptions: { parserOptions: { - project: path.resolve(projectRoot, 'server/tsconfig.json'), + project: path.resolve(projectRoot, 'tsconfig.bun.json'), projectService: false, }, }, }, { - // Its own project because `@types/bun`'s globals conflict with the app's, so it is excluded from - // the root tsconfig and would otherwise belong to no project at all. - files: ['evals/**/*.ts'], + files: ['.github/**/*.{ts,tsx,js}', 'web/proxy.ts', 'config/**/*.{ts,tsx,mts,mjs,cjs,js}'], languageOptions: { parserOptions: { - project: path.resolve(projectRoot, 'evals/tsconfig.json'), - projectService: false, - }, - }, - }, - - { - // CIGitLogic is excluded from the root tsconfig because it needs @types/bun, so type-aware rules have to - // be pointed at the project that does own it. See tests/tooling/README.md. - files: ['tests/tooling/CIGitLogic.test.ts'], - languageOptions: { - parserOptions: { - project: path.resolve(projectRoot, 'tests/tooling/tsconfig.json'), - projectService: false, - }, - }, - }, - - { - // Bun-only scripts are excluded from the root tsconfig because they need @types/bun, so type-aware rules - // have to be pointed at the project that owns them. See scripts/tsconfig.json. - files: ['scripts/applyPatches.ts', 'scripts/lint.ts', 'scripts/typecheck.ts'], - languageOptions: { - parserOptions: { - project: path.resolve(projectRoot, 'scripts/tsconfig.json'), + project: path.resolve(projectRoot, 'tsconfig.node.json'), projectService: false, }, }, diff --git a/config/rsbuild/rsbuild.common.ts b/config/rsbuild/rsbuild.common.ts index 52599fc848cd..91a498bd8a14 100644 --- a/config/rsbuild/rsbuild.common.ts +++ b/config/rsbuild/rsbuild.common.ts @@ -563,7 +563,7 @@ const getCommonConfiguration = async ({file = '.env', platform = 'web', isDevSer ...(sentryWebpackPlugin ? ([ sentryWebpackPlugin({ - authToken: process.env.SENTRY_AUTH_TOKEN as string | undefined, + authToken: process.env.SENTRY_AUTH_TOKEN, org: 'expensify', project: 'app', release: { diff --git a/evals/tsconfig.json b/evals/tsconfig.json deleted file mode 100644 index 1ebdf2aa05a9..000000000000 --- a/evals/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "module": "preserve", - "moduleResolution": "bundler", - "target": "ES2023", - "rootDir": "..", - "noEmit": true, - "strict": true, - // bun-types redeclares node globals, and expensify-common ships ambient jQuery types. Neither is - // ours to fix, and neither affects whether the evals themselves typecheck. - "skipLibCheck": true, - "resolveJsonModule": true, - "types": ["bun"], - "paths": { - "@github/*": ["../.github/*"], - "@prompts/*": ["../prompts/*"], - "@scripts/*": ["../scripts/*", "../.github/scripts/*"] - } - }, - "include": ["."] -} diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json deleted file mode 100644 index fa2371028e14..000000000000 --- a/scripts/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - // Files listed below use Bun's `$` shell (or another Bun-only API), which needs `@types/bun`. Those types - // redeclare globals the app's own types already own (see tests/tooling/tsconfig.json for the same - // tradeoff), so this project confines them to just the files that need them. Everything else in scripts/ - // stays in the root project, where it sees the app's real types. - // - // This can't be widened to the whole scripts/ directory: several scripts (e.g. generateTranslations.ts, - // eslint-report.ts) import from @src/*, which transitively pulls in a large slice of the app. Under this - // project's narrower `types`, that slice loses access to ambient declarations the root project provides - // (asset module types, Onyx's generic augmentations, etc.) and re-typechecks with real errors — e.g. Onyx - // entries collapsing to `Record` and `@assets/*.svg` imports failing to resolve. - // - // To add a new Bun-shell script: add its filename to `files` below. - // - // `include: []` is required: `extends` inherits the root project's `include`, which would otherwise - // pull the whole app in here and reintroduce the same conflict this file exists to avoid. - "extends": "../tsconfig.json", - "compilerOptions": { - "types": ["@types/bun"], - "noEmit": true - }, - "include": [], - "files": ["applyPatches.ts", "lint.ts", "typecheck.ts"] -} diff --git a/scripts/typecheck.ts b/scripts/typecheck.ts index b152d97223fe..62063313cd19 100644 --- a/scripts/typecheck.ts +++ b/scripts/typecheck.ts @@ -4,7 +4,7 @@ * Type-check the repo with the TypeScript 7 native compiler. * * bun scripts/typecheck.ts -> check every project CI gates on - * bun scripts/typecheck.ts evals -> check just the named project directories + * bun scripts/typecheck.ts tsconfig.bun.json -> check just the named project * * Every project is checked even after one fails, so a single run reports every error in the repo. */ @@ -18,14 +18,14 @@ const projectRoot = `${import.meta.dir}/..`; // Invoke that bin by path so a leftover `.bin/tsc` from `@typescript/old` cannot win. const tsc = `${projectRoot}/node_modules/typescript/bin/tsc`; -/** Project directories, relative to the repo root, that `npm run typecheck` and CI check. */ -const DEFAULT_PROJECTS = ['.', 'tests/tooling', 'server', 'server/victory-chart-renderer', 'scripts']; +/** TypeScript projects, relative to the repo root, that `npm run typecheck` and CI check. */ +const DEFAULT_PROJECTS = ['tsconfig.json', 'tsconfig.bun.json', 'tsconfig.node.json', 'server/victory-chart-renderer/tsconfig.json']; const cli = new CLI({ positionalArgs: [ { name: 'projects', - description: 'Project directories to type-check, relative to the repo root (default: the five CI-gated projects)', + description: 'tsconfig paths to type-check, relative to the repo root', variadic: true, default: DEFAULT_PROJECTS, }, @@ -36,13 +36,14 @@ const {projects} = cli.positionalArgs; const failed: string[] = []; for (const project of projects) { - const tsconfig = `${project}/tsconfig.json`; + const tsconfig = project.endsWith('.json') ? project : `${project}/tsconfig.json`; console.log(`\nType checking ${tsconfig}...`); // The build info file lets repeat runs skip unchanged projects. It is named apart from the // `tsconfig.tsbuildinfo` that `incremental` defaults to so that running TypeScript 6 by hand in // the same worktree can't feed it a build info file written by a different compiler. - const result = await $`${tsc} --noEmit --incremental -p ${tsconfig} --tsBuildInfoFile ${project}/tsconfig.ts7.tsbuildinfo`.cwd(projectRoot).nothrow(); + const tsBuildInfoFile = `${tsconfig.replace(/\.json$/, '')}.ts7.tsbuildinfo`; + const result = await $`${tsc} --noEmit --incremental -p ${tsconfig} --tsBuildInfoFile ${tsBuildInfoFile}`.cwd(projectRoot).nothrow(); if (result.exitCode !== 0) { failed.push(tsconfig); } diff --git a/scripts/utils/Git.ts b/scripts/utils/Git.ts index 16e24b00be33..36bc8c7fcd4d 100644 --- a/scripts/utils/Git.ts +++ b/scripts/utils/Git.ts @@ -39,7 +39,7 @@ function execSync(command: string, options?: ExecSyncOptions) { } const IS_CI = process.env.CI === 'true'; -const GITHUB_BASE_REF = process.env.GITHUB_BASE_REF as string | undefined; +const GITHUB_BASE_REF = process.env.GITHUB_BASE_REF; /** * Represents a single changed line in a git diff. diff --git a/server/tsconfig.json b/server/tsconfig.json deleted file mode 100644 index 86c650bb84b4..000000000000 --- a/server/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "types": ["@types/bun"], - "paths": { - "@assets/*": ["../assets/*"], - "@components/*": ["../src/components/*"], - "@github/*": ["../.github/*"], - "@hooks/*": ["../src/hooks/*"], - "@libs/*": ["../src/libs/*"], - "@navigation/*": ["../src/libs/Navigation/*"], - "@pages/*": ["../src/pages/*"], - "@prompts/*": ["../prompts/*"], - "@scripts/*": ["../scripts/*", "../.github/scripts/*"], - "@selectors/*": ["../src/selectors/*"], - "@server/*": ["./*"], - "@src/*": ["../src/*"], - "@styles/*": ["../src/styles/*"], - "@userActions/*": ["../src/libs/actions/*"], - "tests/*": ["../tests/*"] - } - }, - "include": ["libs", "plugins", "stubs"], - "exclude": ["victory-chart-renderer/dist", "victory-chart-renderer/.dev"] -} diff --git a/server/victory-chart-renderer/tsconfig.json b/server/victory-chart-renderer/tsconfig.json index 97e6aa8dd7f3..ffc13e115046 100644 --- a/server/victory-chart-renderer/tsconfig.json +++ b/server/victory-chart-renderer/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "types": ["react-native", "react-native-web", "node"] }, diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 5cc3a5c547b5..3de9f2a471c4 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1,5 +1,3 @@ -import type {SearchFilterKey} from '@components/Search/types'; - import type ResponsiveLayoutResult from '@hooks/useResponsiveLayout/types'; import { @@ -13,13 +11,7 @@ import { MENU_ANIMATION_DURATION as ANIMATION_TIMING_MENU_ANIMATION_DURATION, } from '@libs/Animation/animationTiming'; import MULTIFACTOR_AUTHENTICATION_VALUES from '@libs/MultifactorAuthentication/VALUES'; -import addTrailingForwardSlash from '@libs/UrlUtils'; - -import variables from '@styles/variables'; -import ONYXKEYS from '@src/ONYXKEYS'; -import SCREENS from '@src/SCREENS'; -import type {PolicyTagLists} from '@src/types/onyx'; import type PlaidBankAccount from '@src/types/onyx/PlaidBankAccount'; import type {ValueOf} from 'type-fest'; @@ -27,11 +19,21 @@ import type {ValueOf} from 'type-fest'; /* eslint-disable @typescript-eslint/naming-convention */ import {add as dateAdd} from 'date-fns'; import {sub as dateSubtract} from 'date-fns/sub'; -import Config from 'react-native-config'; -import * as KeyCommand from 'react-native-key-command'; import CI from './CI'; import {LOCALES} from './LOCALES'; +// Isolate runtime-specific dependencies so Bun can import CONST through the platformless fallback. +import CONST_RUNTIME from './runtime'; + +type DefaultPolicyTagLists = Record< + string, + { + name: string; + orderWeight: number; + required: boolean; + tags: Record; + } +>; // Creating a default array and object this way because objects ({}) and arrays ([]) are not stable types. // Freezing the array ensures that it cannot be unintentionally modified. @@ -48,7 +50,7 @@ const DEFAULT_MISSING_ID = -1; const DEFAULT_COUNTRY_CODE = 1; const CLOUDFRONT_DOMAIN = 'cloudfront.net'; const CLOUDFRONT_URL = `https://d2k5nsl2zxldvw.${CLOUDFRONT_DOMAIN}`; -const ACTIVE_EXPENSIFY_URL = addTrailingForwardSlash(Config?.NEW_EXPENSIFY_URL ?? 'https://new.expensify.com'); +const ACTIVE_EXPENSIFY_URL = CONST_RUNTIME.NEW_EXPENSIFY_URL; const USE_EXPENSIFY_URL = 'https://use.expensify.com'; const EXPENSIFY_MOBILE_URL = 'https://expensify.com/mobile'; const EXPENSIFY_URL = 'https://www.expensify.com'; @@ -64,17 +66,19 @@ const MIN_DATE = dateSubtract(new Date(), {years: 20}); const EXPENSIFY_POLICY_DOMAIN = 'expensify-policy'; const EXPENSIFY_POLICY_DOMAIN_EXTENSION = '.exfy'; -const keyModifierControl = KeyCommand?.constants?.keyModifierControl ?? 'keyModifierControl'; -const keyModifierCommand = KeyCommand?.constants?.keyModifierCommand ?? 'keyModifierCommand'; -const keyModifierShift = KeyCommand?.constants?.keyModifierShift ?? 'keyModifierShift'; -const keyModifierShiftControl = KeyCommand?.constants?.keyModifierShiftControl ?? 'keyModifierShiftControl'; -const keyModifierShiftCommand = KeyCommand?.constants?.keyModifierShiftCommand ?? 'keyModifierShiftCommand'; -const keyInputEscape = KeyCommand?.constants?.keyInputEscape ?? 'keyInputEscape'; -const keyInputEnter = KeyCommand?.constants?.keyInputEnter ?? 'keyInputEnter'; -const keyInputUpArrow = KeyCommand?.constants?.keyInputUpArrow ?? 'keyInputUpArrow'; -const keyInputDownArrow = KeyCommand?.constants?.keyInputDownArrow ?? 'keyInputDownArrow'; -const keyInputLeftArrow = KeyCommand?.constants?.keyInputLeftArrow ?? 'keyInputLeftArrow'; -const keyInputRightArrow = KeyCommand?.constants?.keyInputRightArrow ?? 'keyInputRightArrow'; +const { + keyModifierControl, + keyModifierCommand, + keyModifierShift, + keyModifierShiftControl, + keyModifierShiftCommand, + keyInputEscape, + keyInputEnter, + keyInputUpArrow, + keyInputDownArrow, + keyInputLeftArrow, + keyInputRightArrow, +} = CONST_RUNTIME.KEY_COMMANDS; const keyInputSpace = ' '; // describes if a shortcut key can cause navigation @@ -270,7 +274,7 @@ const CONST = { TEXTAREA: 'TEXTAREA', }, POPOVER_ACCOUNT_SWITCHER_POSITION: { - horizontal: 12 + variables.navigationTabBarSize, + horizontal: 12 + CONST_RUNTIME.STYLE_VARIABLES.navigationTabBarSize, vertical: 72, }, POPOVER_DROPDOWN_WIDTH: 334, @@ -3723,27 +3727,7 @@ const CONST = { }, }, - ACCOUNT_ID: { - ACCOUNTING: Number(Config?.EXPENSIFY_ACCOUNT_ID_ACCOUNTING ?? 9645353), - ACCOUNTS_PAYABLE: Number(Config?.EXPENSIFY_ACCOUNT_ID_ACCOUNTS_PAYABLE ?? 10903701), - ADMIN: Number(Config?.EXPENSIFY_ACCOUNT_ID_ADMIN ?? -1), - BILLS: Number(Config?.EXPENSIFY_ACCOUNT_ID_BILLS ?? 1371), - CHRONOS: Number(Config?.EXPENSIFY_ACCOUNT_ID_CHRONOS ?? 10027416), - CONCIERGE: Number(Config?.EXPENSIFY_ACCOUNT_ID_CONCIERGE ?? 8392101), - CONTRIBUTORS: Number(Config?.EXPENSIFY_ACCOUNT_ID_CONTRIBUTORS ?? 9675014), - FIRST_RESPONDER: Number(Config?.EXPENSIFY_ACCOUNT_ID_FIRST_RESPONDER ?? 9375152), - HELP: Number(Config?.EXPENSIFY_ACCOUNT_ID_HELP ?? -1), - INTEGRATION_TESTING_CREDS: Number(Config?.EXPENSIFY_ACCOUNT_ID_INTEGRATION_TESTING_CREDS ?? -1), - NOTIFICATIONS: Number(Config?.EXPENSIFY_ACCOUNT_ID_NOTIFICATIONS ?? 11665625), - PAYROLL: Number(Config?.EXPENSIFY_ACCOUNT_ID_PAYROLL ?? 9679724), - QA: Number(Config?.EXPENSIFY_ACCOUNT_ID_QA ?? 3126513), - QA_TRAVIS: Number(Config?.EXPENSIFY_ACCOUNT_ID_QA_TRAVIS ?? 8595733), - RECEIPTS: Number(Config?.EXPENSIFY_ACCOUNT_ID_RECEIPTS ?? -1), - REWARDS: Number(Config?.EXPENSIFY_ACCOUNT_ID_REWARDS ?? 11023767), // rewards@expensify.com - STUDENT_AMBASSADOR: Number(Config?.EXPENSIFY_ACCOUNT_ID_STUDENT_AMBASSADOR ?? 10476956), - SVFG: Number(Config?.EXPENSIFY_ACCOUNT_ID_SVFG ?? 2012843), - QA_GUIDE: Number(Config?.EXPENSIFY_ACCOUNT_ID_QA_GUIDE ?? 14365522), - }, + ACCOUNT_ID: CONST_RUNTIME.EXPENSIFY_ACCOUNT_IDS, ENVIRONMENT: { DEV: 'development', @@ -4049,7 +4033,7 @@ const CONST = { REPLACE_RECEIPT: 'replaceReceipt', }, COMPACT_RECEIPT: { - MAX_WIDTH: variables.receiptPreviewMaxWidth, + MAX_WIDTH: CONST_RUNTIME.STYLE_VARIABLES.receiptPreviewMaxWidth, DEFAULT_ASPECT_RATIO: 16 / 9, MAX_HEIGHT_PIXEL_ADJUSTMENT: 5, }, @@ -4601,7 +4585,7 @@ const CONST = { required: false, tags: {}, }, - } as PolicyTagLists, + } as DefaultPolicyTagLists, DEFAULT_TAG_NAME: 'Tag', REQUIRE_RECEIPTS_OVER_OPTIONS: { DEFAULT: 'default', @@ -5475,7 +5459,7 @@ const CONST = { // This constant sets a margin equal to the rotate button's height (medium-sized Button (40px)), which is the tallest element in the controls row. // It ensures the controls row isn't cropped if the browser height is reduced too much. - CONTAINER_VERTICAL_MARGIN: variables.componentSizeNormal, + CONTAINER_VERTICAL_MARGIN: CONST_RUNTIME.STYLE_VARIABLES.componentSizeNormal, }, MICROSECONDS_PER_MS: 1000, MILLISECONDS_PER_SECOND: 1000, @@ -7718,15 +7702,7 @@ const CONST = { LAST_12_MONTHS: 'last-12-months', LAST_STATEMENT: 'last-statement', }, - SNAPSHOT_ONYX_KEYS: [ - ONYXKEYS.COLLECTION.REPORT, - ONYXKEYS.COLLECTION.POLICY, - ONYXKEYS.COLLECTION.TRANSACTION, - ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - ONYXKEYS.COLLECTION.REPORT_ACTIONS, - ONYXKEYS.PERSONAL_DETAILS_LIST, - ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, - ], + SNAPSHOT_ONYX_KEYS: CONST_RUNTIME.SEARCH_SNAPSHOT_ONYX_KEYS, SEARCH_KEYS: { EXPENSES: 'expenses', REPORTS: 'reports', @@ -7810,17 +7786,7 @@ const CONST = { CASH_BACK: 'earnedCashback', }, - EXCLUDE_FROM_LAST_VISITED_PATH: [ - SCREENS.NOT_FOUND, - SCREENS.SAML_SIGN_IN, - SCREENS.VALIDATE_LOGIN, - SCREENS.MIGRATED_USER_WELCOME_MODAL.DYNAMIC_ROOT, - SCREENS.SUBMIT_PLAN_WELCOME_MODAL.DYNAMIC_ROOT, - SCREENS.AI_FEATURES_PROMO_MODAL.DYNAMIC_ROOT, - SCREENS.MONEY_REQUEST.STEP_SCAN, - SCREENS.DOMAIN.MEMBERS_MOVE_TO_GROUP, - ...Object.values(SCREENS.MULTIFACTOR_AUTHENTICATION), - ] as string[], + EXCLUDE_FROM_LAST_VISITED_PATH: CONST_RUNTIME.EXCLUDE_FROM_LAST_VISITED_PATH, CANCELLATION_TYPE: { MANUAL: 'manual', @@ -9576,6 +9542,8 @@ const SUBMIT_FEATURE_IDS: ReadonlySet = new Set([ CONST.UPGRADE_FEATURE_INTRO_MAPPING.invoicing.id, ]); +type SearchFilterKey = ValueOf | ValueOf; + const CONTINUATION_DETECTION_SEARCH_FILTER_KEYS = [ CONST.SEARCH.SYNTAX_FILTER_KEYS.TO, CONST.SEARCH.SYNTAX_FILTER_KEYS.FROM, diff --git a/src/CONST/runtime.native.ts b/src/CONST/runtime.native.ts new file mode 100644 index 000000000000..a57cfd5ff9f8 --- /dev/null +++ b/src/CONST/runtime.native.ts @@ -0,0 +1,3 @@ +import CONST_RUNTIME from './runtimeConfigured'; + +export default CONST_RUNTIME; diff --git a/src/CONST/runtime.ts b/src/CONST/runtime.ts new file mode 100644 index 000000000000..dc94525af62e --- /dev/null +++ b/src/CONST/runtime.ts @@ -0,0 +1,4 @@ +import CONST_RUNTIME_DEFAULTS from './runtimeDefaults'; + +// Bun resolves this module without a platform suffix. Web and native bundlers select their configured variants. +export default CONST_RUNTIME_DEFAULTS; diff --git a/src/CONST/runtime.web.ts b/src/CONST/runtime.web.ts new file mode 100644 index 000000000000..a57cfd5ff9f8 --- /dev/null +++ b/src/CONST/runtime.web.ts @@ -0,0 +1,3 @@ +import CONST_RUNTIME from './runtimeConfigured'; + +export default CONST_RUNTIME; diff --git a/src/CONST/runtimeConfigured.ts b/src/CONST/runtimeConfigured.ts new file mode 100644 index 000000000000..9d28e4e51bcb --- /dev/null +++ b/src/CONST/runtimeConfigured.ts @@ -0,0 +1,82 @@ +import addTrailingForwardSlash from '@libs/UrlUtils'; + +import variables from '@styles/variables'; + +import ONYXKEYS from '@src/ONYXKEYS'; +import SCREENS from '@src/SCREENS'; + +import Config from 'react-native-config'; +import * as KeyCommand from 'react-native-key-command'; + +import type {ConstRuntime} from './runtimeDefaults'; + +import CONST_RUNTIME_DEFAULTS from './runtimeDefaults'; + +/** + * Web and native builds resolve their platform files to this implementation, which replaces the + * Bun-safe defaults with values from React Native Config, key commands, styles, Onyx, and screens. + */ +const CONST_RUNTIME: ConstRuntime = { + NEW_EXPENSIFY_URL: addTrailingForwardSlash(Config?.NEW_EXPENSIFY_URL ?? CONST_RUNTIME_DEFAULTS.NEW_EXPENSIFY_URL), + KEY_COMMANDS: { + keyModifierControl: KeyCommand?.constants?.keyModifierControl ?? CONST_RUNTIME_DEFAULTS.KEY_COMMANDS.keyModifierControl, + keyModifierCommand: KeyCommand?.constants?.keyModifierCommand ?? CONST_RUNTIME_DEFAULTS.KEY_COMMANDS.keyModifierCommand, + keyModifierShift: KeyCommand?.constants?.keyModifierShift ?? CONST_RUNTIME_DEFAULTS.KEY_COMMANDS.keyModifierShift, + keyModifierShiftControl: KeyCommand?.constants?.keyModifierShiftControl ?? CONST_RUNTIME_DEFAULTS.KEY_COMMANDS.keyModifierShiftControl, + keyModifierShiftCommand: KeyCommand?.constants?.keyModifierShiftCommand ?? CONST_RUNTIME_DEFAULTS.KEY_COMMANDS.keyModifierShiftCommand, + keyInputEscape: KeyCommand?.constants?.keyInputEscape ?? CONST_RUNTIME_DEFAULTS.KEY_COMMANDS.keyInputEscape, + keyInputEnter: KeyCommand?.constants?.keyInputEnter ?? CONST_RUNTIME_DEFAULTS.KEY_COMMANDS.keyInputEnter, + keyInputUpArrow: KeyCommand?.constants?.keyInputUpArrow ?? CONST_RUNTIME_DEFAULTS.KEY_COMMANDS.keyInputUpArrow, + keyInputDownArrow: KeyCommand?.constants?.keyInputDownArrow ?? CONST_RUNTIME_DEFAULTS.KEY_COMMANDS.keyInputDownArrow, + keyInputLeftArrow: KeyCommand?.constants?.keyInputLeftArrow ?? CONST_RUNTIME_DEFAULTS.KEY_COMMANDS.keyInputLeftArrow, + keyInputRightArrow: KeyCommand?.constants?.keyInputRightArrow ?? CONST_RUNTIME_DEFAULTS.KEY_COMMANDS.keyInputRightArrow, + }, + STYLE_VARIABLES: { + componentSizeNormal: variables.componentSizeNormal, + navigationTabBarSize: variables.navigationTabBarSize, + receiptPreviewMaxWidth: variables.receiptPreviewMaxWidth, + }, + EXPENSIFY_ACCOUNT_IDS: { + ACCOUNTING: Number(Config?.EXPENSIFY_ACCOUNT_ID_ACCOUNTING ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.ACCOUNTING), + ACCOUNTS_PAYABLE: Number(Config?.EXPENSIFY_ACCOUNT_ID_ACCOUNTS_PAYABLE ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.ACCOUNTS_PAYABLE), + ADMIN: Number(Config?.EXPENSIFY_ACCOUNT_ID_ADMIN ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.ADMIN), + BILLS: Number(Config?.EXPENSIFY_ACCOUNT_ID_BILLS ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.BILLS), + CHRONOS: Number(Config?.EXPENSIFY_ACCOUNT_ID_CHRONOS ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.CHRONOS), + CONCIERGE: Number(Config?.EXPENSIFY_ACCOUNT_ID_CONCIERGE ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.CONCIERGE), + CONTRIBUTORS: Number(Config?.EXPENSIFY_ACCOUNT_ID_CONTRIBUTORS ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.CONTRIBUTORS), + FIRST_RESPONDER: Number(Config?.EXPENSIFY_ACCOUNT_ID_FIRST_RESPONDER ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.FIRST_RESPONDER), + HELP: Number(Config?.EXPENSIFY_ACCOUNT_ID_HELP ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.HELP), + INTEGRATION_TESTING_CREDS: Number(Config?.EXPENSIFY_ACCOUNT_ID_INTEGRATION_TESTING_CREDS ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.INTEGRATION_TESTING_CREDS), + NOTIFICATIONS: Number(Config?.EXPENSIFY_ACCOUNT_ID_NOTIFICATIONS ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.NOTIFICATIONS), + PAYROLL: Number(Config?.EXPENSIFY_ACCOUNT_ID_PAYROLL ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.PAYROLL), + QA: Number(Config?.EXPENSIFY_ACCOUNT_ID_QA ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.QA), + QA_TRAVIS: Number(Config?.EXPENSIFY_ACCOUNT_ID_QA_TRAVIS ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.QA_TRAVIS), + RECEIPTS: Number(Config?.EXPENSIFY_ACCOUNT_ID_RECEIPTS ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.RECEIPTS), + REWARDS: Number(Config?.EXPENSIFY_ACCOUNT_ID_REWARDS ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.REWARDS), + STUDENT_AMBASSADOR: Number(Config?.EXPENSIFY_ACCOUNT_ID_STUDENT_AMBASSADOR ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.STUDENT_AMBASSADOR), + SVFG: Number(Config?.EXPENSIFY_ACCOUNT_ID_SVFG ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.SVFG), + QA_GUIDE: Number(Config?.EXPENSIFY_ACCOUNT_ID_QA_GUIDE ?? CONST_RUNTIME_DEFAULTS.EXPENSIFY_ACCOUNT_IDS.QA_GUIDE), + }, + SEARCH_SNAPSHOT_ONYX_KEYS: [ + ONYXKEYS.COLLECTION.REPORT, + ONYXKEYS.COLLECTION.POLICY, + ONYXKEYS.COLLECTION.TRANSACTION, + ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + ONYXKEYS.COLLECTION.REPORT_ACTIONS, + ONYXKEYS.PERSONAL_DETAILS_LIST, + ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, + ], + EXCLUDE_FROM_LAST_VISITED_PATH: [ + SCREENS.NOT_FOUND, + SCREENS.SAML_SIGN_IN, + SCREENS.VALIDATE_LOGIN, + SCREENS.MIGRATED_USER_WELCOME_MODAL.DYNAMIC_ROOT, + SCREENS.SUBMIT_PLAN_WELCOME_MODAL.DYNAMIC_ROOT, + SCREENS.AI_FEATURES_PROMO_MODAL.DYNAMIC_ROOT, + SCREENS.MONEY_REQUEST.STEP_SCAN, + SCREENS.DOMAIN.MEMBERS_MOVE_TO_GROUP, + ...Object.values(SCREENS.MULTIFACTOR_AUTHENTICATION), + ], +}; + +export default CONST_RUNTIME; diff --git a/src/CONST/runtimeDefaults.ts b/src/CONST/runtimeDefaults.ts new file mode 100644 index 000000000000..0562c91da7c8 --- /dev/null +++ b/src/CONST/runtimeDefaults.ts @@ -0,0 +1,111 @@ +/** + * Values that CONST needs from platform-specific modules, plus Bun-safe fallbacks. + * Keep this shape runtime-agnostic so translation tooling can import the full CONST barrel. + */ +type ConstRuntime = { + NEW_EXPENSIFY_URL: string; + KEY_COMMANDS: { + keyModifierControl: string; + keyModifierCommand: string; + keyModifierShift: string; + keyModifierShiftControl: string; + keyModifierShiftCommand: string; + keyInputEscape: string; + keyInputEnter: string; + keyInputUpArrow: string; + keyInputDownArrow: string; + keyInputLeftArrow: string; + keyInputRightArrow: string; + }; + STYLE_VARIABLES: { + componentSizeNormal: number; + navigationTabBarSize: number; + receiptPreviewMaxWidth: number; + }; + EXPENSIFY_ACCOUNT_IDS: { + ACCOUNTING: number; + ACCOUNTS_PAYABLE: number; + ADMIN: number; + BILLS: number; + CHRONOS: number; + CONCIERGE: number; + CONTRIBUTORS: number; + FIRST_RESPONDER: number; + HELP: number; + INTEGRATION_TESTING_CREDS: number; + NOTIFICATIONS: number; + PAYROLL: number; + QA: number; + QA_TRAVIS: number; + RECEIPTS: number; + REWARDS: number; + STUDENT_AMBASSADOR: number; + SVFG: number; + QA_GUIDE: number; + }; + SEARCH_SNAPSHOT_ONYX_KEYS: readonly string[]; + EXCLUDE_FROM_LAST_VISITED_PATH: readonly string[]; +}; + +const CONST_RUNTIME_DEFAULTS: ConstRuntime = { + NEW_EXPENSIFY_URL: 'https://new.expensify.com/', + KEY_COMMANDS: { + keyModifierControl: 'keyModifierControl', + keyModifierCommand: 'keyModifierCommand', + keyModifierShift: 'keyModifierShift', + keyModifierShiftControl: 'keyModifierShiftControl', + keyModifierShiftCommand: 'keyModifierShiftCommand', + keyInputEscape: 'keyInputEscape', + keyInputEnter: 'keyInputEnter', + keyInputUpArrow: 'keyInputUpArrow', + keyInputDownArrow: 'keyInputDownArrow', + keyInputLeftArrow: 'keyInputLeftArrow', + keyInputRightArrow: 'keyInputRightArrow', + }, + STYLE_VARIABLES: { + componentSizeNormal: 40, + navigationTabBarSize: 72, + receiptPreviewMaxWidth: 440, + }, + EXPENSIFY_ACCOUNT_IDS: { + ACCOUNTING: 9645353, + ACCOUNTS_PAYABLE: 10903701, + ADMIN: -1, + BILLS: 1371, + CHRONOS: 10027416, + CONCIERGE: 8392101, + CONTRIBUTORS: 9675014, + FIRST_RESPONDER: 9375152, + HELP: -1, + INTEGRATION_TESTING_CREDS: -1, + NOTIFICATIONS: 11665625, + PAYROLL: 9679724, + QA: 3126513, + QA_TRAVIS: 8595733, + RECEIPTS: -1, + REWARDS: 11023767, + STUDENT_AMBASSADOR: 10476956, + SVFG: 2012843, + QA_GUIDE: 14365522, + }, + SEARCH_SNAPSHOT_ONYX_KEYS: ['report_', 'policy_', 'transactions_', 'transactionViolations_', 'reportActions_', 'personalDetailsList', 'reportNameValuePairs_'], + EXCLUDE_FROM_LAST_VISITED_PATH: [ + 'not-found', + 'SAMLSignIn', + 'ValidateLogin', + 'Dynamic_MigratedUserWelcomeModal_Root', + 'Dynamic_SubmitPlanWelcomeModal_Root', + 'Dynamic_AIFeaturesPromoModal_Root', + 'Money_Request_Step_Scan', + 'Members_Move_To_Group', + 'Multifactor_Authentication_Validate_Code', + 'Multifactor_Authentication_Outcome_Success', + 'Multifactor_Authentication_Outcome_Failure', + 'Multifactor_Authentication_Prompt', + 'Multifactor_Authentication_Revoke', + 'Multifactor_Authentication_Authorize_Transaction', + ], +}; + +export default CONST_RUNTIME_DEFAULTS; +export type {ConstRuntime}; diff --git a/src/languages/TranslationTypes.ts b/src/languages/TranslationTypes.ts new file mode 100644 index 000000000000..c155756e0f85 --- /dev/null +++ b/src/languages/TranslationTypes.ts @@ -0,0 +1,93 @@ +import type CONST from '@src/CONST'; +import type DeepValueOf from '@src/types/utils/DeepValueOf'; + +import type {ValueOf} from 'type-fest'; + +type Country = keyof typeof CONST.ALL_COUNTRIES; + +type OnboardingTaskLinks = Partial<{ + onboardingCompanySize: ValueOf; + integrationName: string; + workspaceSettingsLink: string; + workspaceCategoriesLink: string; + workspaceTagsLink: string; + workspaceMoreFeaturesLink: string; + workspaceMembersLink: string; + workspaceAccountingLink: string; + workspaceConfirmationLink: string; + testDriveURL: string; + corporateCardLink: string; +}>; + +type OnboardingTask = { + type: ValueOf; + autoCompleted: boolean; + title: string | ((params: OnboardingTaskLinks) => string); + description: string | ((params: OnboardingTaskLinks) => string); +}; + +type ReportAction = { + reportActionID: string; + actionName: DeepValueOf; + created: string; +}; + +type OnyxInputOrEntry = TOnyxValue | null | undefined; + +type OriginalMessageReportPreview = { + linkedReportID: string; + whisperedTo?: number[]; +}; + +type OriginalMessageSettlementAccountLocked = { + maskedBankAccountNumber: string; + policyID: string; +}; + +type PolicyRuleTaxRate = { + externalID: string; + value: string; + name: string; +}; + +type PolicyRulesModifiedFields = { + merchant?: string; + category?: string; + tag?: string; + comment?: string; + description?: string; + billable?: boolean; + reimbursable?: boolean; + tax?: { + // eslint-disable-next-line @typescript-eslint/naming-convention + field_id_TAX: PolicyRuleTaxRate; + }; +}; + +type PersonalRulesModifiedFields = PolicyRulesModifiedFields & { + reportName?: string; +}; + +type ConnectionName = ValueOf; +type AllConnectionName = ConnectionName; +type PolicyConnectionSyncStage = ValueOf; +type SageIntacctMappingName = ValueOf; +type DelegateRole = ValueOf; +type ViolationDataType = ValueOf; + +export type { + AllConnectionName, + ConnectionName, + Country, + DelegateRole, + OnboardingTask, + OnyxInputOrEntry, + OriginalMessageReportPreview, + OriginalMessageSettlementAccountLocked, + PersonalRulesModifiedFields, + PolicyConnectionSyncStage, + PolicyRulesModifiedFields, + ReportAction, + SageIntacctMappingName, + ViolationDataType, +}; diff --git a/src/languages/de.ts b/src/languages/de.ts index c5a95fd86d98..d8518c4d5571 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -10,14 +10,13 @@ * - Improve context annotations in src/languages/en.ts */ import type {OnboardingTask} from '@libs/actions/Welcome/OnboardingFlow'; -import StringUtils from '@libs/StringUtils'; +import startsWithVowel from '@libs/StringUtils/startsWithVowel'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import type {OnyxInputOrEntry, ReportAction} from '@src/types/onyx'; import type {DelegateRole} from '@src/types/onyx/Account'; -import type OriginalMessage from '@src/types/onyx/OriginalMessage'; -import type {OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; +import type {OriginalMessageReportPreview, OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; import type {AllConnectionName, ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; import type {ViolationDataType} from '@src/types/onyx/TransactionViolation'; @@ -1542,7 +1541,7 @@ const translations: TranslationDeepObject = { basedOnAI: 'basierend auf bisherigen Aktivitäten', basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `basierend auf den Workspace-Regeln` : 'basierend auf dem Workspace-Regelwerk'), threadExpenseReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} ${comment ? `für ${comment}` : 'Ausgabe'}`, - invoiceReportName: ({linkedReportID}: OriginalMessage) => `Rechnungsbericht Nr. ${linkedReportID}`, + invoiceReportName: ({linkedReportID}: OriginalMessageReportPreview) => `Rechnungsbericht Nr. ${linkedReportID}`, threadPaySomeoneReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} gesendet${comment ? `für ${comment}` : ''}`, movedFromPersonalSpace: (reportName?: string, workspaceName?: string) => `Ausgabe von persönlichem Bereich nach ${workspaceName ?? `Chat mit ${reportName}`} verschoben`, movedToPersonalSpace: 'Ausgabe in persönlichen Bereich verschoben', @@ -1866,7 +1865,7 @@ const translations: TranslationDeepObject = { pageTitle: 'Wählen Sie die Details aus, die Sie behalten möchten:', noDifferences: 'Keine Unterschiede zwischen den Transaktionen gefunden', pleaseSelectError: ({field}: {field: string}) => { - const article = StringUtils.startsWithVowel(field) ? 'an' : 'a'; + const article = startsWithVowel(field) ? 'an' : 'eine'; return `Bitte wählen Sie ${article} ${field} aus`; }, pleaseSelectAttendees: 'Bitte wählen Sie Teilnehmende aus', diff --git a/src/languages/el.ts b/src/languages/el.ts index fa9475245423..11ab71370c74 100644 --- a/src/languages/el.ts +++ b/src/languages/el.ts @@ -10,12 +10,11 @@ * - Improve context annotations in src/languages/en.ts */ import type {OnboardingTask} from '@libs/actions/Welcome/OnboardingFlow'; -import StringUtils from '@libs/StringUtils'; +import startsWithVowel from '@libs/StringUtils/startsWithVowel'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; -import type OriginalMessage from '@src/types/onyx/OriginalMessage'; -import type {OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; +import type {OriginalMessageReportPreview, OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; import type {ValueOf} from 'type-fest'; @@ -1265,7 +1264,6 @@ const translations: TranslationDeepObject = { if (count === 0) { return duplicates > 0 ? 'Δεν προστέθηκαν κανόνες εμπόρων, καθώς υπάρχουν ήδη όλοι.' : 'Δεν έχουν προστεθεί κανόνες εμπόρου.'; } - return { one: 'Προστέθηκε 1 κανόνας εμπόρου.', other: `Έχουν προστεθεί ${count} κανόνες εμπόρων.`, @@ -1597,7 +1595,7 @@ const translations: TranslationDeepObject = { basedOnAI: 'βάσει προηγούμενης δραστηριότητας', basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `βάσει των κανόνων του χώρου εργασίας` : 'βάσει κανόνα χώρου εργασίας'), threadExpenseReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} ${comment ? `για ${comment}` : 'δαπάνη'}`, - invoiceReportName: ({linkedReportID}: OriginalMessage) => `Αναφορά τιμολογίου #${linkedReportID}`, + invoiceReportName: ({linkedReportID}: OriginalMessageReportPreview) => `Αναφορά τιμολογίου #${linkedReportID}`, threadPaySomeoneReportName: (formattedAmount: string, comment?: string) => `στάλθηκαν ${formattedAmount}${comment ? `για ${comment}` : ''}`, movedFromPersonalSpace: (reportName, workspaceName) => `μετακινήθηκε η δαπάνη από τον προσωπικό χώρο στο ${workspaceName ?? `συνομιλήστε με τον/την ${reportName}`}`, movedToPersonalSpace: 'μετακινήθηκε η δαπάνη στον προσωπικό χώρο', @@ -1916,7 +1914,7 @@ const translations: TranslationDeepObject = { pageTitle: 'Επιλέξτε τις λεπτομέρειες που θέλετε να διατηρήσετε:', noDifferences: 'Δεν βρέθηκαν διαφορές μεταξύ των συναλλαγών', pleaseSelectError: ({field}: {field: string}) => { - const article = StringUtils.startsWithVowel(field) ? 'ένα' : 'α'; + const article = startsWithVowel(field) ? 'ένα' : 'α'; return `Παρακαλούμε επιλέξτε ${article} ${field}`; }, pleaseSelectAttendees: 'Παρακαλώ επιλέξτε συμμετέχοντες', diff --git a/src/languages/en.ts b/src/languages/en.ts index f7753748e0a3..054df710877e 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1,14 +1,6 @@ -import type {OnboardingTask} from '@libs/actions/Welcome/OnboardingFlow'; -import StringUtils from '@libs/StringUtils'; +import startsWithVowel from '@libs/StringUtils/startsWithVowel'; import CONST from '@src/CONST'; -import type {Country} from '@src/CONST'; -import type {OnyxInputOrEntry, ReportAction} from '@src/types/onyx'; -import type {DelegateRole} from '@src/types/onyx/Account'; -import type OriginalMessage from '@src/types/onyx/OriginalMessage'; -import type {OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; -import type {AllConnectionName, ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; -import type {ViolationDataType} from '@src/types/onyx/TransactionViolation'; import type {ValueOf} from 'type-fest'; @@ -16,6 +8,22 @@ import {CONST as COMMON_CONST, Str} from 'expensify-common'; import startCase from 'lodash/startCase'; import type {ExportAgainModalDescriptionParams, ExportIntegrationSelectedParams} from './params'; +import type { + AllConnectionName, + ConnectionName, + Country, + DelegateRole, + OnboardingTask, + OnyxInputOrEntry, + OriginalMessageReportPreview, + OriginalMessageSettlementAccountLocked, + PersonalRulesModifiedFields, + PolicyConnectionSyncStage, + PolicyRulesModifiedFields, + ReportAction, + SageIntacctMappingName, + ViolationDataType, +} from './TranslationTypes'; import type {TranslationDeepObject} from './types'; type StateValue = { @@ -1615,7 +1623,7 @@ const translations = { basedOnAI: 'based on past activity', basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `based on workspace rules` : 'based on workspace rule'), threadExpenseReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} ${comment ? `for ${comment}` : 'expense'}`, - invoiceReportName: ({linkedReportID}: OriginalMessage) => `Invoice Report #${linkedReportID}`, + invoiceReportName: ({linkedReportID}: OriginalMessageReportPreview) => `Invoice Report #${linkedReportID}`, threadPaySomeoneReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`, movedFromPersonalSpace: (reportName?: string, workspaceName?: string) => `moved expense from personal space to ${workspaceName ?? `chat with ${reportName}`}`, movedToPersonalSpace: 'moved expense to personal space', @@ -1926,7 +1934,7 @@ const translations = { pageTitle: 'Select the details you want to keep:', noDifferences: 'No differences found between the transactions', pleaseSelectError: ({field}: {field: string}) => { - const article = StringUtils.startsWithVowel(field) ? 'an' : 'a'; + const article = startsWithVowel(field) ? 'an' : 'a'; return `Please select ${article} ${field}`; }, pleaseSelectAttendees: 'Please select attendees', diff --git a/src/languages/es.ts b/src/languages/es.ts index f90ba1f6dc78..655ed5d29da6 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -9,8 +9,9 @@ * - Improve the prompts in prompts/translation, or * - Improve context annotations in src/languages/en.ts */ + import CONST from '@src/CONST'; -import type {OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; +import type {OriginalMessageReportPreview, OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; import {CONST as COMMON_CONST, Str} from 'expensify-common'; @@ -1189,7 +1190,6 @@ const translations: TranslationDeepObject = { if (count === 0) { return duplicates > 0 ? 'No se han añadido reglas de comerciante, ya que todas ya existen.' : 'No se han añadido reglas de comerciante.'; } - return { one: 'Se ha añadido 1 regla de comerciante.', other: `Se han añadido ${count} reglas de comerciante.`, @@ -1540,7 +1540,7 @@ const translations: TranslationDeepObject = { basedOnAI: 'basado en actividad pasada', basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `basado en reglas del espacio de trabajo` : 'basado en regla del espacio de trabajo'), threadExpenseReportName: (formattedAmount, comment) => `${comment ? `${formattedAmount} para ${comment}` : `Gasto de ${formattedAmount}`}`, - invoiceReportName: ({linkedReportID}) => `Informe de facturación #${linkedReportID}`, + invoiceReportName: ({linkedReportID}: OriginalMessageReportPreview) => `Informe de factura n.º ${linkedReportID}`, threadPaySomeoneReportName: (formattedAmount, comment) => `${formattedAmount} enviado${comment ? ` para ${comment}` : ''}`, movedFromPersonalSpace: (reportName, workspaceName) => `movió el gasto desde su espacio personal a ${workspaceName ?? `un chat con ${reportName}`}`, movedToPersonalSpace: 'movió el gasto a su espacio personal', @@ -1852,7 +1852,9 @@ const translations: TranslationDeepObject = { header: 'Selecciona los detalles', pageTitle: 'Selecciona los detalles que deseas conservar:', noDifferences: 'No se encontraron diferencias entre las transacciones', - pleaseSelectError: ({field}) => `Por favor, selecciona un(a) ${field}`, + pleaseSelectError: ({field}: {field: string}) => { + return `Por favor, selecciona un(a) ${field}`; + }, pleaseSelectAttendees: 'Por favor, selecciona asistentes', selectAllDetailsError: 'Selecciona todos los detalles antes de continuar.', }, diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 38c034a3cef3..f84f7aa8f21b 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -10,14 +10,13 @@ * - Improve context annotations in src/languages/en.ts */ import type {OnboardingTask} from '@libs/actions/Welcome/OnboardingFlow'; -import StringUtils from '@libs/StringUtils'; +import startsWithVowel from '@libs/StringUtils/startsWithVowel'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import type {OnyxInputOrEntry, ReportAction} from '@src/types/onyx'; import type {DelegateRole} from '@src/types/onyx/Account'; -import type OriginalMessage from '@src/types/onyx/OriginalMessage'; -import type {OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; +import type {OriginalMessageReportPreview, OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; import type {AllConnectionName, ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; import type {ViolationDataType} from '@src/types/onyx/TransactionViolation'; @@ -1546,7 +1545,7 @@ const translations: TranslationDeepObject = { basedOnMCC: ({rulesLink}: {rulesLink: string}) => rulesLink ? `en fonction des règles de l’espace de travail` : 'en fonction de la règle de l’espace de travail', threadExpenseReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} ${comment ? `pour ${comment}` : 'dépense'}`, - invoiceReportName: ({linkedReportID}: OriginalMessage) => `Note de frais de facture n° ${linkedReportID}`, + invoiceReportName: ({linkedReportID}: OriginalMessageReportPreview) => `Note de frais de facture n° ${linkedReportID}`, threadPaySomeoneReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} envoyé${comment ? `pour ${comment}` : ''}`, movedFromPersonalSpace: (reportName?: string, workspaceName?: string) => `a déplacé la dépense de l’espace personnel vers ${workspaceName ?? `discuter avec ${reportName}`}`, movedToPersonalSpace: 'a déplacé la dépense vers l’espace personnel', @@ -1872,7 +1871,7 @@ const translations: TranslationDeepObject = { pageTitle: 'Sélectionnez les détails que vous souhaitez conserver :', noDifferences: 'Aucune différence trouvée entre les transactions', pleaseSelectError: ({field}: {field: string}) => { - const article = StringUtils.startsWithVowel(field) ? 'un' : 'un'; + const article = startsWithVowel(field) ? 'un' : 'un'; return `Veuillez sélectionner ${article} ${field}`; }, pleaseSelectAttendees: 'Veuillez sélectionner les participants', diff --git a/src/languages/it.ts b/src/languages/it.ts index 87a38c608105..9b5d6c84244e 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -10,14 +10,13 @@ * - Improve context annotations in src/languages/en.ts */ import type {OnboardingTask} from '@libs/actions/Welcome/OnboardingFlow'; -import StringUtils from '@libs/StringUtils'; +import startsWithVowel from '@libs/StringUtils/startsWithVowel'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import type {OnyxInputOrEntry, ReportAction} from '@src/types/onyx'; import type {DelegateRole} from '@src/types/onyx/Account'; -import type OriginalMessage from '@src/types/onyx/OriginalMessage'; -import type {OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; +import type {OriginalMessageReportPreview, OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; import type {AllConnectionName, ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; import type {ViolationDataType} from '@src/types/onyx/TransactionViolation'; @@ -1540,7 +1539,7 @@ const translations: TranslationDeepObject = { basedOnAI: 'in base all’attività precedente', basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `in base alle regole dello spazio di lavoro` : 'in base alle regole della workspace'), threadExpenseReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} ${comment ? `per ${comment}` : 'spesa'}`, - invoiceReportName: ({linkedReportID}: OriginalMessage) => `Report fattura n. ${linkedReportID}`, + invoiceReportName: ({linkedReportID}: OriginalMessageReportPreview) => `Report fattura n. ${linkedReportID}`, threadPaySomeoneReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} inviato${comment ? `per ${comment}` : ''}`, movedFromPersonalSpace: (reportName?: string, workspaceName?: string) => `ha spostato la spesa dallo spazio personale a ${workspaceName ?? `chatta con ${reportName}`}`, movedToPersonalSpace: 'ha spostato la spesa nello spazio personale', @@ -1863,7 +1862,7 @@ const translations: TranslationDeepObject = { pageTitle: 'Seleziona i dettagli che vuoi mantenere:', noDifferences: 'Nessuna differenza trovata tra le transazioni', pleaseSelectError: ({field}: {field: string}) => { - const article = StringUtils.startsWithVowel(field) ? 'un' : 'a'; + const article = startsWithVowel(field) ? 'un' : 'a'; return `Seleziona ${article} ${field}`; }, pleaseSelectAttendees: 'Seleziona i partecipanti', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index fdb22137b305..824cfbc79882 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -10,14 +10,13 @@ * - Improve context annotations in src/languages/en.ts */ import type {OnboardingTask} from '@libs/actions/Welcome/OnboardingFlow'; -import StringUtils from '@libs/StringUtils'; +import startsWithVowel from '@libs/StringUtils/startsWithVowel'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import type {OnyxInputOrEntry, ReportAction} from '@src/types/onyx'; import type {DelegateRole} from '@src/types/onyx/Account'; -import type OriginalMessage from '@src/types/onyx/OriginalMessage'; -import type {OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; +import type {OriginalMessageReportPreview, OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; import type {AllConnectionName, ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; import type {ViolationDataType} from '@src/types/onyx/TransactionViolation'; @@ -1521,7 +1520,7 @@ const translations: TranslationDeepObject = { basedOnAI: '過去のアクティビティに基づく', basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `ワークスペースルールに基づく` : 'ワークスペースのルールに基づく'), threadExpenseReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} ${comment ? `${comment} 用` : '経費'}`, - invoiceReportName: ({linkedReportID}: OriginalMessage) => `請求書レポート #${linkedReportID}`, + invoiceReportName: ({linkedReportID}: OriginalMessageReportPreview) => `請求書レポート #${linkedReportID}`, threadPaySomeoneReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} を送信済み${comment ? `${comment}用` : ''}`, movedFromPersonalSpace: (reportName?: string, workspaceName?: string) => `経費を個人スペースから${workspaceName ?? `${reportName}とチャット`}に移動しました`, movedToPersonalSpace: '経費を個人スペースに移動しました', @@ -1841,8 +1840,8 @@ const translations: TranslationDeepObject = { pageTitle: '保持したい詳細を選択してください:', noDifferences: '取引間に差異は見つかりませんでした', pleaseSelectError: ({field}: {field: string}) => { - const article = StringUtils.startsWithVowel(field) ? 'ある' : 'a'; - return `${article} の${field}を選択してください`; + const article = startsWithVowel(field) ? '1つの' : 'a'; + return `${article} ${field} を選択してください`; }, pleaseSelectAttendees: '出席者を選択してください', selectAllDetailsError: '続行する前にすべての詳細を選択してください。', @@ -9024,9 +9023,6 @@ ${reportName}`, reject: '却下', duplicateExpense: () => ({ one: '経費を複製', - // Japanese has no grammatical plural, so `Intl.PluralRules` selects `other` for every count, - // including 1. The single/bulk distinction here is semantic rather than grammatical, so `other` - // branches on the count itself to keep 一括 (bulk) on the multi-expense action only. other: (count: number) => (count === 1 ? '経費を複製' : '経費を一括複製'), }), noOptionsAvailable: '選択した経費グループには利用できるオプションがありません。', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index b20d5623514f..b8910e304fd7 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -10,14 +10,13 @@ * - Improve context annotations in src/languages/en.ts */ import type {OnboardingTask} from '@libs/actions/Welcome/OnboardingFlow'; -import StringUtils from '@libs/StringUtils'; +import startsWithVowel from '@libs/StringUtils/startsWithVowel'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import type {OnyxInputOrEntry, ReportAction} from '@src/types/onyx'; import type {DelegateRole} from '@src/types/onyx/Account'; -import type OriginalMessage from '@src/types/onyx/OriginalMessage'; -import type {OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; +import type {OriginalMessageReportPreview, OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; import type {AllConnectionName, ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; import type {ViolationDataType} from '@src/types/onyx/TransactionViolation'; @@ -1538,7 +1537,7 @@ const translations: TranslationDeepObject = { basedOnAI: 'op basis van eerdere activiteit', basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `op basis van werkruimteregels` : 'op basis van werkruimteregel'), threadExpenseReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} ${comment ? `voor ${comment}` : 'uitgave'}`, - invoiceReportName: ({linkedReportID}: OriginalMessage) => `Factuurrapport nr. ${linkedReportID}`, + invoiceReportName: ({linkedReportID}: OriginalMessageReportPreview) => `Factuurrapport nr. ${linkedReportID}`, threadPaySomeoneReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} verzonden${comment ? `voor ${comment}` : ''}`, movedFromPersonalSpace: (reportName?: string, workspaceName?: string) => `heeft uitgave verplaatst van persoonlijke ruimte naar ${workspaceName ?? `chat met ${reportName}`}`, movedToPersonalSpace: 'heeft uitgave verplaatst naar persoonlijke ruimte', @@ -1858,8 +1857,8 @@ const translations: TranslationDeepObject = { pageTitle: 'Selecteer de details die je wilt behouden:', noDifferences: 'Geen verschillen gevonden tussen de transacties', pleaseSelectError: ({field}: {field: string}) => { - const article = StringUtils.startsWithVowel(field) ? 'een' : 'een'; - return `Selecteer ${article} ${field}`; + const article = startsWithVowel(field) ? 'een' : 'een'; + return `Selecteer alsjeblieft ${article} ${field}`; }, pleaseSelectAttendees: 'Selecteer aanwezigen', selectAllDetailsError: 'Selecteer alle details voordat je verdergaat.', diff --git a/src/languages/params.ts b/src/languages/params.ts index cdae2871706a..bde06b208cd8 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -1,4 +1,4 @@ -import type {ConnectionName} from '@src/types/onyx/Policy'; +import type {ConnectionName} from './TranslationTypes'; type StepCounterParams = {step: number; total?: number; text?: string}; diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 7055403c8e4d..ff689acb9e96 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -10,14 +10,13 @@ * - Improve context annotations in src/languages/en.ts */ import type {OnboardingTask} from '@libs/actions/Welcome/OnboardingFlow'; -import StringUtils from '@libs/StringUtils'; +import startsWithVowel from '@libs/StringUtils/startsWithVowel'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import type {OnyxInputOrEntry, ReportAction} from '@src/types/onyx'; import type {DelegateRole} from '@src/types/onyx/Account'; -import type OriginalMessage from '@src/types/onyx/OriginalMessage'; -import type {OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; +import type {OriginalMessageReportPreview, OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; import type {AllConnectionName, ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; import type {ViolationDataType} from '@src/types/onyx/TransactionViolation'; @@ -1567,7 +1566,7 @@ const translations: TranslationDeepObject = { basedOnAI: 'na podstawie dotychczasowej aktywności', basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `na podstawie zasad przestrzeni roboczej` : 'na podstawie reguły przestrzeni roboczej'), threadExpenseReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} ${comment ? `dla ${comment}` : 'wydatek'}`, - invoiceReportName: ({linkedReportID}: OriginalMessage) => `Raport faktury nr ${linkedReportID}`, + invoiceReportName: ({linkedReportID}: OriginalMessageReportPreview) => `Raport faktury nr ${linkedReportID}`, threadPaySomeoneReportName: (formattedAmount: string, comment?: string) => `Wysłano ${formattedAmount}${comment ? `za ${comment}` : ''}`, movedFromPersonalSpace: (reportName?: string, workspaceName?: string) => `przeniesiono wydatek z przestrzeni osobistej do ${workspaceName ?? `czat z ${reportName}`}`, movedToPersonalSpace: 'przeniesiono wydatek do przestrzeni prywatnej', @@ -1890,7 +1889,7 @@ const translations: TranslationDeepObject = { pageTitle: 'Wybierz szczegóły, które chcesz zachować:', noDifferences: 'Nie znaleziono różnic między transakcjami', pleaseSelectError: ({field}: {field: string}) => { - const article = StringUtils.startsWithVowel(field) ? 'włączony' : 'a'; + const article = startsWithVowel(field) ? 'jeden' : 'a'; return `Wybierz ${article} ${field}`; }, pleaseSelectAttendees: 'Wybierz uczestników', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index b72a7b75bbb5..c721d02f4449 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -10,14 +10,13 @@ * - Improve context annotations in src/languages/en.ts */ import type {OnboardingTask} from '@libs/actions/Welcome/OnboardingFlow'; -import StringUtils from '@libs/StringUtils'; +import startsWithVowel from '@libs/StringUtils/startsWithVowel'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import type {OnyxInputOrEntry, ReportAction} from '@src/types/onyx'; import type {DelegateRole} from '@src/types/onyx/Account'; -import type OriginalMessage from '@src/types/onyx/OriginalMessage'; -import type {OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; +import type {OriginalMessageReportPreview, OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; import type {AllConnectionName, ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; import type {ViolationDataType} from '@src/types/onyx/TransactionViolation'; @@ -1537,7 +1536,7 @@ const translations: TranslationDeepObject = { basedOnAI: 'com base na atividade anterior', basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `com base nas regras do workspace` : 'com base na regra do workspace'), threadExpenseReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} ${comment ? `para ${comment}` : 'despesa'}`, - invoiceReportName: ({linkedReportID}: OriginalMessage) => `Relatório de fatura nº ${linkedReportID}`, + invoiceReportName: ({linkedReportID}: OriginalMessageReportPreview) => `Relatório de fatura nº ${linkedReportID}`, threadPaySomeoneReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} enviado${comment ? `para ${comment}` : ''}`, movedFromPersonalSpace: (reportName?: string, workspaceName?: string) => `moveu a despesa do espaço pessoal para ${workspaceName ?? `conversar com ${reportName}`}`, movedToPersonalSpace: 'moveu a despesa para o espaço pessoal', @@ -1853,7 +1852,7 @@ const translations: TranslationDeepObject = { pageTitle: 'Selecione os detalhes que você quer manter:', noDifferences: 'Nenhuma diferença encontrada entre as transações', pleaseSelectError: ({field}: {field: string}) => { - const article = StringUtils.startsWithVowel(field) ? 'um' : 'a'; + const article = startsWithVowel(field) ? 'um' : 'um'; return `Selecione ${article} ${field}`; }, pleaseSelectAttendees: 'Selecione participantes', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 4ebbb34297b9..7c961fe952c9 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -10,14 +10,13 @@ * - Improve context annotations in src/languages/en.ts */ import type {OnboardingTask} from '@libs/actions/Welcome/OnboardingFlow'; -import StringUtils from '@libs/StringUtils'; +import startsWithVowel from '@libs/StringUtils/startsWithVowel'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import type {OnyxInputOrEntry, ReportAction} from '@src/types/onyx'; import type {DelegateRole} from '@src/types/onyx/Account'; -import type OriginalMessage from '@src/types/onyx/OriginalMessage'; -import type {OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; +import type {OriginalMessageReportPreview, OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; import type {AllConnectionName, ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; import type {ViolationDataType} from '@src/types/onyx/TransactionViolation'; @@ -1479,7 +1478,7 @@ const translations: TranslationDeepObject = { basedOnAI: '基于过去的活动', basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `基于工作区规则` : '基于工作区规则'), threadExpenseReportName: (formattedAmount: string, comment?: string) => `${formattedAmount} ${comment ? `用于 ${comment}` : '报销'}`, - invoiceReportName: ({linkedReportID}: OriginalMessage) => `发票报告 #${linkedReportID}`, + invoiceReportName: ({linkedReportID}: OriginalMessageReportPreview) => `发票报表 #${linkedReportID}`, threadPaySomeoneReportName: (formattedAmount: string, comment?: string) => `已发送 ${formattedAmount}${comment ? `用于 ${comment}` : ''}`, movedFromPersonalSpace: (reportName?: string, workspaceName?: string) => `已将报销从个人空间移动到 ${workspaceName ?? `与 ${reportName} 聊天`}`, movedToPersonalSpace: '已将报销移动到个人空间', @@ -1782,7 +1781,7 @@ const translations: TranslationDeepObject = { pageTitle: '选择要保留的详细信息:', noDifferences: '在这些交易之间未发现差异', pleaseSelectError: ({field}: {field: string}) => { - const article = StringUtils.startsWithVowel(field) ? '一个' : 'a'; + const article = startsWithVowel(field) ? '一个' : '一个'; return `请选择${article}${field}`; }, pleaseSelectAttendees: '请选择参会者', diff --git a/src/libs/StringUtils/index.ts b/src/libs/StringUtils/index.ts index 3831730c3fdd..08ca28e461a7 100644 --- a/src/libs/StringUtils/index.ts +++ b/src/libs/StringUtils/index.ts @@ -9,6 +9,7 @@ import deburr from 'lodash/deburr'; import decodeUnicode from './decodeUnicode'; import hash from './hash'; +import startsWithVowel from './startsWithVowel'; /** * Removes diacritical marks and non-alphabetic and non-latin characters from a string. @@ -202,15 +203,6 @@ function countWhiteSpaces(str: string): number { return (str.match(/\s/g) ?? []).length; } -/** - * Check if the string starts with a vowel - * @param str - The input string - * @returns True if the string starts with a vowel, false otherwise - */ -function startsWithVowel(str: string): boolean { - return /^[aeiouAEIOU]/.test(str); -} - function camelToHyphenCase(str: string) { return str.replaceAll(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`); } diff --git a/src/libs/StringUtils/startsWithVowel.ts b/src/libs/StringUtils/startsWithVowel.ts new file mode 100644 index 000000000000..296bad53b598 --- /dev/null +++ b/src/libs/StringUtils/startsWithVowel.ts @@ -0,0 +1,6 @@ +/** Returns whether a string begins with an English vowel. */ +function startsWithVowel(str: string): boolean { + return /^[aeiouAEIOU]/.test(str); +} + +export default startsWithVowel; diff --git a/src/styles/theme/colors.ts b/src/styles/theme/colors.ts index cf04f36b9636..adb9f5b4d5eb 100644 --- a/src/styles/theme/colors.ts +++ b/src/styles/theme/colors.ts @@ -1,10 +1,10 @@ -import type {Color} from './types'; - /** * DO NOT import colors.js into files. Use the theme switching hooks and HOCs instead. * For functional components, you can use the `useTheme` and `useThemeStyles` hooks * For class components, you can use the `withTheme` and `withThemeStyles` HOCs */ +type Color = string; + const colors: Record = { // Brand Colors black: '#000000', @@ -98,3 +98,4 @@ const colors: Record = { }; export default colors; +export type {Color}; diff --git a/src/styles/theme/types.ts b/src/styles/theme/types.ts index f4a0ae9ac17b..d1a4f83d901a 100644 --- a/src/styles/theme/types.ts +++ b/src/styles/theme/types.ts @@ -5,8 +5,7 @@ import type CONST from '@src/CONST'; import type {ValueOf} from 'type-fest'; import type {ColorScheme, StatusBarStyle} from '..'; - -type Color = string; +import type {Color} from './colors'; type ThemePreference = ValueOf; type ThemePreferenceWithoutSystem = Exclude; diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 41b3d6fc6f41..d624c3ded360 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -1851,6 +1851,7 @@ export default OriginalMessage; export type { DecisionName, OriginalMessageIOU, + OriginalMessageReportPreview, ChronosOOOEvent, PaymentMethodType, OriginalMessageSource, diff --git a/tests/tooling/README.md b/tests/tooling/README.md index 9a6e63c9164e..4642e9b9d732 100644 --- a/tests/tooling/README.md +++ b/tests/tooling/README.md @@ -71,19 +71,10 @@ Because `mock.module()` is hoisting-sensitive, files that use it import the modu ## Type-checking -These files are type-checked by the root `tsconfig.json` along with everything else, so they see the app's real -types. `bun:test` resolves because that config pulls in `node_modules/bun-types/test.d.ts` — the one file in -bun-types that declares the module — through `files` rather than `include`, since `exclude` covers node_modules. - -The rest of bun-types is deliberately left out: its global JSX declarations are incompatible with the app's React -types, and `generateTranslations.test.ts` reaches `src/` through the script it covers. One consequence is that -`@types/jest`'s globals are visible here too, so a missing `bun:test` import can type-check but still fail at -runtime — import every helper you use. - -`CIGitLogic.test.ts` is the exception. It uses Bun's `$` shell, which is a runtime API rather than a module -declaration, so it needs the full `@types/bun`. Those types redeclare globals the app already owns — a `jest` -namespace that shadows `@types/jest`'s generic signatures, and a `fetch` carrying `preconnect` — and adding them -to the root project produces ~1,100 errors across `tests/unit`. So that one file is excluded from the root -project and type-checked by `tests/tooling/tsconfig.json`, which mirrors what `server/tsconfig.json` does. A new -test needing Bun runtime APIs should be added to that project's `files`; one that only needs `bun:test` should -not, so it keeps seeing the app's types. +These files are type-checked by the directory-scoped Bun project, `tsconfig.bun.json`. They get Bun and Node types, +including `bun:test` and Bun runtime APIs such as `$`. Add a new tooling test directly under `tests/tooling/`; no +tsconfig update is needed. + +The Bun project avoids general app imports because Bun's globals conflict with the React Native and Jest globals used +by the app. Translation tooling may import `src/languages/en.ts`: its CONST dependencies use Bun-safe defaults, while +web and native builds resolve platform-specific runtime values. diff --git a/tests/tooling/TranslationImport.test.ts b/tests/tooling/TranslationImport.test.ts new file mode 100644 index 000000000000..ad055150d1f4 --- /dev/null +++ b/tests/tooling/TranslationImport.test.ts @@ -0,0 +1,11 @@ +import {describe, expect, it} from 'bun:test'; + +import CONST from '@src/CONST'; +import en from '@src/languages/en'; + +describe('translation imports', () => { + it('loads real translations with Bun-safe CONST defaults', () => { + expect(CONST.NEW_EXPENSIFY_URL).toBe('https://new.expensify.com/'); + expect(en.multifactorAuthentication.unsupportedDevice.pleaseUseWebApp).toContain(`href="${CONST.NEW_EXPENSIFY_URL}"`); + }); +}); diff --git a/tests/tooling/setup.ts b/tests/tooling/setup.ts index f4ca502ad5d1..0ac88765bf50 100644 --- a/tests/tooling/setup.ts +++ b/tests/tooling/setup.ts @@ -6,8 +6,8 @@ import {jest} from 'bun:test'; // GitHub Actions always sets GITHUB_REPOSITORY in CI, but local runs need a default, mirroring jest/setup.ts's // equivalent fallback for the test files Jest still owns. if (!('GITHUB_REPOSITORY' in process.env)) { - (process.env as NodeJS.ProcessEnv).GITHUB_REPOSITORY_OWNER = 'Expensify'; - (process.env as NodeJS.ProcessEnv).GITHUB_REPOSITORY = 'Expensify/App'; + process.env.GITHUB_REPOSITORY_OWNER = 'Expensify'; + process.env.GITHUB_REPOSITORY = 'Expensify/App'; } // The code under test logs heavily, which drowns out the actual results. Jest's CI runs pass --silent for the same diff --git a/tests/tooling/tsconfig.json b/tests/tooling/tsconfig.json deleted file mode 100644 index 89e7d0f63b6d..000000000000 --- a/tests/tooling/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - // CIGitLogic is the one test here that uses Bun's runtime API — the `$` shell — which needs `@types/bun`. - // Those types redeclare globals that the app's own types already own: a `jest` namespace that shadows - // @types/jest's generic signatures, and a `fetch` carrying `preconnect`. Loading them into the root project - // produces ~1,100 errors across tests/unit, so this project confines them to the single file that needs them. - // Everything else in tests/tooling stays in the root project, where it sees the app's real types. - // - // `include: []` is required: `extends` inherits the root project's `include`, which would otherwise pull the - // whole app in here and reintroduce the same conflict this file exists to avoid. - "extends": "../../tsconfig.json", - "compilerOptions": { - "types": ["@types/bun"], - "noEmit": true - }, - "include": [], - "files": ["CIGitLogic.test.ts"] -} diff --git a/tests/unit/CONSTRuntime.test.ts b/tests/unit/CONSTRuntime.test.ts new file mode 100644 index 000000000000..5f3633d2ca48 --- /dev/null +++ b/tests/unit/CONSTRuntime.test.ts @@ -0,0 +1,22 @@ +import CONST_RUNTIME from '@src/CONST/runtime.web'; + +jest.mock('react-native-config', () => ({ + NEW_EXPENSIFY_URL: 'https://staging.example.com', + EXPENSIFY_ACCOUNT_ID_ACCOUNTING: '123', +})); + +jest.mock('react-native-key-command', () => ({ + constants: { + keyInputEnter: 'configuredEnter', + }, +})); + +describe('CONST runtime values', () => { + it('uses configured values with defaults for missing values', () => { + expect(CONST_RUNTIME.NEW_EXPENSIFY_URL).toBe('https://staging.example.com/'); + expect(CONST_RUNTIME.EXPENSIFY_ACCOUNT_IDS.ACCOUNTING).toBe(123); + expect(CONST_RUNTIME.EXPENSIFY_ACCOUNT_IDS.CONCIERGE).toBe(8392101); + expect(CONST_RUNTIME.KEY_COMMANDS.keyInputEnter).toBe('configuredEnter'); + expect(CONST_RUNTIME.KEY_COMMANDS.keyInputEscape).toBe('keyInputEscape'); + }); +}); diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 000000000000..eca65ec97f85 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "expo/tsconfig.base", + "compilerOptions": { + "isolatedModules": true, + "strict": true, + "incremental": true, + "paths": { + "@assets/*": ["./assets/*"], + "@components/*": ["./src/components/*"], + "@github/*": ["./.github/*"], + "@hooks/*": ["./src/hooks/*"], + "@libs/*": ["./src/libs/*"], + "@navigation/*": ["./src/libs/Navigation/*"], + "@pages/*": ["./src/pages/*"], + "@prompts/*": ["./prompts/*"], + "@scripts/*": ["./scripts/*", "./.github/scripts/*"], + "@server/*": ["./server/*"], + "@styles/*": ["./src/styles/*"], + "@src/*": ["./src/*"], + "@userActions/*": ["./src/libs/actions/*"], + "@selectors/*": ["./src/selectors/*"], + "tests/*": ["./tests/*"] + } + } +} diff --git a/tsconfig.bun.json b/tsconfig.bun.json new file mode 100644 index 000000000000..7e5d2ae4b42c --- /dev/null +++ b/tsconfig.bun.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "types": ["bun"], + "lib": ["ESNext"] + }, + "include": ["scripts", "tests/tooling", "server/libs", "server/plugins", "server/stubs", "evals"], + "exclude": ["**/node_modules/*", "**/dist/*", "server/victory-chart-renderer/**"] +} diff --git a/tsconfig.json b/tsconfig.json index 42baf407b6bb..1e76f426b439 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,10 @@ { "$schema": "https://json.schemastore.org/tsconfig", - "extends": "expo/tsconfig.base", + "extends": "./tsconfig.base.json", "compilerOptions": { "types": ["react-native", "react-native-web", "jest", "node"], "lib": ["DOM", "ESNext"], - "isolatedModules": true, - "strict": true, - "incremental": true, + // Babel reads this config directly and does not resolve its `extends` chain. "paths": { "@assets/*": ["./assets/*"], "@components/*": ["./src/components/*"], @@ -17,6 +15,7 @@ "@pages/*": ["./src/pages/*"], "@prompts/*": ["./prompts/*"], "@scripts/*": ["./scripts/*", "./.github/scripts/*"], + "@server/*": ["./server/*"], "@styles/*": ["./src/styles/*"], "@src/*": ["./src/*"], "@userActions/*": ["./src/libs/actions/*"], @@ -24,43 +23,20 @@ "tests/*": ["./tests/*"] } }, - // bun:test's module declaration, for tests/tooling. Referenced through `files` rather than `include` - // because `exclude` below covers node_modules and would otherwise drop it. This is the one file in bun-types - // that declares the module; the package's other declarations (notably its global JSX namespace) are - // deliberately not pulled in, as they conflict with the app's React types. - "files": ["node_modules/bun-types/test.d.ts"], - "include": [ - "src", - "web", - "website", - "docs", - "assets", - "config", - "tests", - "jest", - "prompts", - "__mocks__", - ".github/**/*", - ".storybook/**/*", - ".claude/**/*", - "scripts", - "modules", - "**/*.nitro/*.ts", - "**/*.nitro/*.tsx" - ], - // CIGitLogic, Bun-only scripts and everything under evals/ use Bun's APIs, so they need @types/bun, whose globals - // conflict with this project's. They are type-checked by tests/tooling/tsconfig.json, - // scripts/tsconfig.json and evals/tsconfig.json instead — see the comments there. + "include": ["src", "website", "docs", "assets", "tests", "jest", "prompts", "__mocks__", ".storybook/**/*", ".claude/**/*", "modules", "**/*.nitro/*.ts", "**/*.nitro/*.tsx"], "exclude": [ "**/node_modules/*", "**/dist/*", ".github/actions/**/index.js", "**/docs/*", ".claude/worktrees/**", - "tests/tooling/CIGitLogic.test.ts", - "scripts/applyPatches.ts", - "scripts/lint.ts", - "scripts/typecheck.ts", - "evals" + "tests/tooling", + "server/libs", + "server/plugins", + "server/stubs", + "evals", + ".github", + "web/proxy.ts", + "config" ] } diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 000000000000..21d512a9facd --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "types": ["node"], + "lib": ["ESNext"] + }, + "include": [".github/**/*", "web/proxy.ts", "config/**/*", "config/**/*.d.mts"], + "exclude": ["**/node_modules/*", "**/dist/*", ".github/actions/**/index.js"] +}