Skip to content

Commit c836844

Browse files
committed
resolve conflict
2 parents cd5ab5a + 20eee1c commit c836844

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+827
-807
lines changed

.github/ISSUE_TEMPLATE/OnboardOffboardExpertContributor.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@ Which action do you wish to take for this team member (select one):
3434

3535
### Ring0 Tasks
3636

37-
- [ ] Add to / remove from the appropriate GitHub child team of [external-expert-contributors](https://github.com/orgs/Expensify/teams/external-expert-contributors/teams) (each agency must have its own child team)
37+
- [ ] If adding, add to the appropriate GitHub child team of [external-expert-contributors](https://github.com/orgs/Expensify/teams/external-expert-contributors/teams) (each agency must have its own child team)
38+
- [ ] If removing, remove from our organization [here](https://github.com/orgs/Expensify/people)

Mobile-Expensify

README.md

Lines changed: 0 additions & 327 deletions
Large diffs are not rendered by default.

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ android {
114114
minSdkVersion rootProject.ext.minSdkVersion
115115
targetSdkVersion rootProject.ext.targetSdkVersion
116116
multiDexEnabled rootProject.ext.multiDexEnabled
117-
versionCode 1009021614
118-
versionName "9.2.16-14"
117+
versionCode 1009021702
118+
versionName "9.2.17-2"
119119
// Supported language variants must be declared here to avoid from being removed during the compilation.
120120
// This also helps us to not include unnecessary language variants in the APK.
121121
resConfigs "en", "es"

contributingGuides/philosophies/DATA-BINDING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The UI binds to the data stored on the local device's database (Onyx).
1111
## Rules
1212
### - The UI MUST use `useOnyx` to get data and subscribe to changes of that data in Onyx
1313
### - The UI MUST NOT call any Onyx methods directly
14-
### - The UI MUST trigger an an action when something needs to happen
14+
### - The UI MUST trigger an action when something needs to happen
1515
For example, a person inputs data, the UI calls an action and passes the user's input.
1616

1717
### - The UI SHOULD anticipate missing or incomplete data

contributingGuides/philosophies/DATA-FLOW.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,8 @@ This is how data flows through the app:
1414
6. Go to 1
1515

1616
![New Expensify Data Flow Chart](/contributingGuides/philosophies/data_flow.png)
17+
18+
## Rules
19+
20+
### - API commands SHOULD return created/updated data directly
21+
When adding new API commands, always prefer to return the created/updated data in the command itself, instead of saving and reloading. For example: if we call `CreateTransaction`, we SHOULD prefer making `CreateTransaction` return the data it just created instead of calling `CreateTransaction` then `Get` rvl=transactionList.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# HybridApp Philosophy
2+
This describes how the HybridApp architecture works and the conventions that MUST be followed when working with it.
3+
4+
#### Terminology
5+
- **HybridApp** - The production app containing both "Expensify Classic" and "New Expensify"
6+
- **NewDot** - The new Expensify application (this repository)
7+
- **Mobile-Expensify** - Git submodule containing Expensify Classic code
8+
- **Standalone** - NewDot running independently without Classic integration
9+
10+
## Overview
11+
12+
Currently, the production Expensify app contains both "Expensify Classic" and "New Expensify". The file structure is as follows:
13+
14+
- 📂 [**App**](https://github.com/Expensify/App)
15+
- 📂 [**android**](https://github.com/Expensify/App/tree/main/android): New Expensify Android specific code (not a part of HybridApp native code)
16+
- 📂 [**ios**](https://github.com/Expensify/App/tree/main/ios): New Expensify iOS specific code (not a part of HybridApp native code)
17+
- 📂 [**src**](https://github.com/Expensify/App/tree/main/src): New Expensify TypeScript logic
18+
- 📂 [**Mobile-Expensify**](https://github.com/Expensify/Mobile-Expensify): `git` submodule that is pointed to [Mobile-Expensify](https://github.com/Expensify/Mobile-Expensify)
19+
- 📂 [**Android**](https://github.com/Expensify/Mobile-Expensify/tree/main/Android): Expensify Classic Android specific code
20+
- 📂 [**iOS**](https://github.com/Expensify/Mobile-Expensify/tree/main/iOS): Expensify Classic iOS specific code
21+
- 📂 [**app**](https://github.com/Expensify/Mobile-Expensify/tree/main/app): Expensify Classic JavaScript logic (aka YAPL)
22+
23+
You can only build HybridApp if you have been granted access to [`Mobile-Expensify`](https://github.com/Expensify/Mobile-Expensify). For most contributors, you will be working on the standalone NewDot application.
24+
25+
## Rules
26+
27+
### - Access to Mobile-Expensify repository is REQUIRED to build HybridApp
28+
For most contributors, you SHOULD work on the standalone NewDot application instead.
29+
30+
### - Git submodule MUST be properly initialized and configured
31+
When working with HybridApp:
32+
33+
1. Initialize the submodule: `git submodule init`
34+
2. Update the submodule: `git submodule update`
35+
3. Configure automatic updates: `git config --global submodule.recurse true`
36+
37+
### - HybridApp native code MUST be accessed through Mobile-Expensify directories
38+
The native code for HybridApp is located in:
39+
- `./Mobile-Expensify/Android` (not `./android`)
40+
- `./Mobile-Expensify/iOS` (not `./ios`)
41+
42+
Changes to `./android` and `./ios` folders at the root **will NOT affect HybridApp builds**.
43+
44+
### - IDEs MUST open the correct workspace for HybridApp development
45+
To open HybridApp projects:
46+
- **Android Studio**: `./Mobile-Expensify/Android`
47+
- **Xcode**: `./Mobile-Expensify/iOS/Expensify.xcworkspace`
48+
49+
### - Scripts automatically target HybridApp when Mobile-Expensify is present
50+
Default npm scripts target HybridApp when the submodule exists:
51+
52+
| Command | Description |
53+
| --------------------- | ---------------------------------- |
54+
| `npm run android` | Build **HybridApp** for Android |
55+
| `npm run ios` | Build **HybridApp** for iOS |
56+
| `npm run ipad` | Build **HybridApp** for iPad |
57+
| `npm run ipad-sm` | Build **HybridApp** for small iPad |
58+
| `npm run pod-install` | Install pods for **HybridApp** |
59+
| `npm run clean` | Clean native code of **HybridApp** |
60+
61+
### - Standalone scripts MUST be used when targeting NewDot only
62+
Append `-standalone` to target standalone NewDot:
63+
64+
| Command | Description |
65+
| -------------------------------- | ----------------------------------------------------------- |
66+
| `npm run install-standalone` | Install standalone **NewDot** node modules (`npm install`) |
67+
| `npm run clean-standalone` | Clean native code for standalone **NewDot** |
68+
| `npm run android-standalone` | Build **NewDot** for Android in standalone mode |
69+
| `npm run ios-standalone` | Build **NewDot** for iOS in standalone mode |
70+
| `npm run pod-install-standalone` | Install pods for standalone **NewDot** |
71+
| `npm run ipad-standalone` | Build **NewDot** for iPad in standalone mode |
72+
| `npm run ipad-sm-standalone` | Build **NewDot** for small iPad in standalone mode |
73+
74+
## Setup Instructions
75+
76+
### Initial Setup
77+
1. Follow [NewDot setup instructions](https://github.com/Expensify/App?tab=readme-ov-file#getting-started) first
78+
2. Initialize submodule: `git submodule init`
79+
3. Update submodule: `git submodule update`
80+
- For faster setup: `git submodule update --init --progress --depth 100`
81+
4. Configure Git for automatic submodule updates: `git config --global submodule.recurse true`
82+
83+
### Git Configuration
84+
If you have access to `Mobile-Expensify` and commands fail, add to `~/.gitconfig`:
85+
86+
```
87+
[url "https://github.com/"]
88+
insteadOf = ssh://git@github.com/
89+
```
90+
91+
To prevent submodule changes from appearing in `git status`, add to `.git/config`:
92+
93+
```
94+
[submodule "Mobile-Expensify"]
95+
ignore = all
96+
```
97+
98+
### External Contributors and C+ Contributors
99+
If you need to modify `Mobile-Expensify` source code:
100+
101+
1. Create your own fork of Mobile-Expensify
102+
2. Swap the origin: `cd Mobile-Expensify && git remote set-url origin <YOUR_FORK_URL>`
103+
104+
## Submodule Management
105+
106+
### - Submodule updates SHOULD be done carefully
107+
The `Mobile-Expensify` directory points to a specific commit. To update:
108+
109+
- Download latest changes: `git submodule update --remote`
110+
- Manual update: Switch branches and pull within the `Mobile-Expensify` directory
111+
112+
### - Submodule state MUST be considered when switching branches
113+
When switching branches, run `git submodule update` to ensure compatibility.
114+
115+
## HybridApp-Specific Features
116+
117+
### Patches
118+
- Patches are applied automatically during `npm install`
119+
- Add HybridApp-specific patches: `npx patch-package <PACKAGE_NAME> --patch-dir Mobile-Expensify/patches`
120+
121+
### Additional Resources
122+
For extended documentation, troubleshooting, and pro tips, refer to [HYBRID_APP.md](contributingGuides/HYBRID_APP.md).

contributingGuides/philosophies/INDEX.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S
88
* [Cross-Platform Philosophy](/contributingGuides/philosophies/CROSS-PLATFORM.md)
99
* [Data Flow Philosophy](/contributingGuides/philosophies/DATA-FLOW.md)
1010
* [Data Binding Philosophy](/contributingGuides/philosophies/DATA-BINDING.md)
11-
* [Onyx Data Management Philosophy](/contributingGuides/philosophies/ONYX-DATA-MANAGEMENT.md)
1211
* [Directory Structure and File Naming Philosophy](/contributingGuides/philosophies/DIRECTORIES.md)
12+
* [Internationalization Philosophy](/contributingGuides/philosophies/INTERNATIONALIZATION.md)
13+
* [HybridApp Philosophy](/contributingGuides/philosophies/HYBRID-APP.md)
1314
* [Offline Philosophy](/contributingGuides/philosophies/OFFLINE.md)
15+
* [Onyx Data Management Philosophy](/contributingGuides/philosophies/ONYX-DATA-MANAGEMENT.md)
1416
* [Routing Philosophy](/contributingGuides/philosophies/ROUTING.md)
1517
* [Security Philosophy](/contributingGuides/philosophies/SECURITY.md)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Internationalization Philosophy
2+
This application is built with Internationalization (I18n) / Localization (L10n) support to provide a consistent experience across different languages and regions.
3+
4+
#### Related Philosophies
5+
- [Data Binding Philosophy](/contributingGuides/philosophies/DATA-BINDING.md)
6+
7+
#### Terminology
8+
- **I18n** - Internationalization, the process of designing software to support multiple languages
9+
- **L10n** - Localization, the process of adapting software for specific languages and regions
10+
- **Translation Keys** - Unique identifiers for translatable strings
11+
- **Locale** - A specific language and region combination (e.g., en-US, es-ES)
12+
13+
## Rules
14+
15+
### - All user-facing content MUST be localized
16+
The following types of data MUST always be localized when presented to the user (including accessibility texts that are not rendered):
17+
18+
- **Texts**: Use the [translate method](https://github.com/Expensify/App/blob/655ba416d552d5c88e57977a6e0165fb7eb7ab58/src/libs/translate.js#L15)
19+
- **Date/time**: Use [DateUtils](https://github.com/Expensify/App/blob/f579946fbfbdc62acc5bd281dc75cabb803d9af0/src/libs/DateUtils.js)
20+
- **Numbers and amounts**: Use [NumberFormatUtils](https://github.com/Expensify/App/blob/55b2372d1344e3b61854139806a53f8a3d7c2b8b/src/libs/NumberFormatUtils.js) and [LocaleDigitUtils](https://github.com/Expensify/App/blob/55b2372d1344e3b61854139806a53f8a3d7c2b8b/src/libs/LocaleDigitUtils.js)
21+
- **Phone numbers**: Use [LocalPhoneNumber](https://github.com/Expensify/App/blob/bdfbafe18ee2d60f766c697744f23fad64b62cad/src/libs/LocalePhoneNumber.js#L51-L52)
22+
23+
### - Components MUST use the `useLocalize` hook for translations
24+
In most cases, you will need to localize data used in a component. Use the [useLocalize](https://github.com/Expensify/App/blob/4510fc76bbf5df699a2575bfb49a276af90f3ed7/src/hooks/useLocalize.ts) hook, which abstracts most of the logic you need (primarily subscribing to the [NVP_PREFERRED_LOCALE](https://github.com/Expensify/App/blob/6cf1a56df670a11bf61aa67eeb64c1f87161dea1/src/ONYXKEYS.js#L88) Onyx key).
25+
26+
### - Translations MUST be organized by feature and stored in language files
27+
All translations are stored in language files in [src/languages](https://github.com/Expensify/App/tree/b114bc86ff38e3feca764e75b3f5bf4f60fcd6fe/src/languages). Translations SHOULD be grouped by their pages/components for better organization.
28+
29+
### - Common phrases SHOULD be shared when used in multiple places
30+
A common rule of thumb is to move a common word/phrase to be shared when it's used in 3 or more places.
31+
32+
### - Complex translation strings MUST use arrow function format
33+
Always prefer longer and more complex strings in the translation files. For example, if you need to generate the text `User has sent $20.00 to you on Oct 25th at 10:05am`, add just one key to the translation file and use the arrow function version:
34+
35+
```javascript
36+
nameOfTheKey: ({amount, dateTime}) => `User has sent ${amount} to you on ${datetime}`,
37+
```
38+
39+
This is because the order of phrases might vary from one language to another.
40+
41+
### - Plural forms MUST be handled correctly using plural translation objects
42+
When working with translations that involve plural forms, it's important to handle different cases correctly:
43+
44+
- **zero**: Used when there are no items **(optional)**
45+
- **one**: Used when there's exactly one item
46+
- **two**: Used when there's two items **(optional)**
47+
- **few**: Used for a small number of items **(optional)**
48+
- **many**: Used for larger quantities **(optional)**
49+
- **other**: A catch-all case for other counts or variations
50+
51+
Example implementation:
52+
```javascript
53+
messages: () => ({
54+
zero: 'No messages',
55+
one: 'One message',
56+
two: 'Two messages',
57+
few: (count) => `${count} messages`,
58+
many: (count) => `You have ${count} messages`,
59+
other: (count) => `You have ${count} unread messages`,
60+
})
61+
```
62+
63+
Usage in code:
64+
```javascript
65+
translate('common.messages', {count: 1});
66+
```
67+
68+
## Translation Generation
69+
70+
### - `src/languages/en.ts` MUST be the source of truth for static strings
71+
`src/languages/en.ts` is the source of truth for static strings in the App. `src/languages/es.ts` is (for now) manually-curated. The remainder are AI-generated using `scripts/generateTranslations.ts`.
72+
73+
### - Translation script SHOULD be used for generating non-English translations
74+
The script is run automatically in GH and a diff with the translations is posted as a comment. See example: https://github.com/Expensify/App/pull/70702#issuecomment-3312988591
75+
76+
### - Translation quality SHOULD be improved through context and prompt refinement
77+
If you are unhappy with the results of an AI translation, there are two methods of recourse:
78+
79+
1. **Context annotations**: If you are adding a string that can have an ambiguous meaning without proper context, you can add a context annotation in `en.ts`. This takes the form of a comment before your string starting with `@context`.
80+
81+
2. **Prompt adjustment**: The base prompt(s) can be found in `prompts/translation`, and can be adjusted if necessary.

contributingGuides/philosophies/ONYX-DATA-MANAGEMENT.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,31 @@ Example of creating a new optimistic comment:
2121
3. Comment is created in the server
2222
4. Server responds
2323
5. UI updates with data from the server
24+
25+
### - Collections SHOULD be stored as individual keys when components bind directly to them
26+
Store collections as individual keys+ID (e.g., `report_1234`, `report_4567`) when a component will bind directly to one of those keys. For example: reports are stored as individual keys because `OptionRow.js` binds to the individual report keys for each link. However, report actions are stored as an array of objects because nothing binds directly to a single report action.
27+
28+
### - Onyx keys MUST be defined using constants in `ONYXKEYS`
29+
Each Onyx key represents either a collection of items or a specific entry in storage. For example, since all reports are stored as individual keys like `report_1234`, if code needs to know about all the reports (e.g., display a list of them in the nav menu), then it MUST subscribe to the key `ONYXKEYS.COLLECTION.REPORT`.
30+
31+
### - Storage eviction MUST be configured for non-critical data
32+
Different platforms come with varying storage capacities and Onyx has a way to gracefully fail when those storage limits are encountered.
33+
34+
**To flag a key as safe for removal:**
35+
- Add the key to the `evictableKeys` option in `Onyx.init(options)`
36+
- Implement `canEvict` in the Onyx config for each component subscribing to a key
37+
- The key will only be deleted when all subscribers return `true` for `canEvict`
38+
39+
Example:
40+
```js
41+
Onyx.init({
42+
evictableKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
43+
});
44+
45+
export default withOnyx({
46+
reportActions: {
47+
key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
48+
canEvict: props => !props.isActiveReport,
49+
},
50+
})(ReportActionsView);
51+
```

0 commit comments

Comments
 (0)