Skip to content

Commit 7d4bfd0

Browse files
committed
Merge branch 'main' into mkzie2-issue/67833
2 parents dac21dc + 111cfbf commit 7d4bfd0

275 files changed

Lines changed: 8798 additions & 8872 deletions

File tree

Some content is hidden

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

.github/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"extends": "../tsconfig.json",
23
"compilerOptions": {
34
"module": "commonjs",
45
"target": "ES2023",

.github/workflows/generateTranslations.yml

Lines changed: 142 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,158 @@ name: Generate static translations
33
on:
44
pull_request:
55
types: [opened, synchronize]
6-
paths-ignore: ['docs/articles/**/*.md', 'docs/redirects.csv', 'docs/assets/images/**']
6+
paths: ['src/languages/en.ts']
77

88
jobs:
9-
# We always run dry-run the script to verify that it still works.
10-
# The generateTranslations script runs with ts-node, which can't handle Flow
11-
# (the specialized JS that React Native is written in).
12-
# Therefore, adding an import in the wrong place could break the script, even if you didn't modify the script.
13-
dryRun:
9+
generateTranslations:
1410
runs-on: ubuntu-latest
1511
steps:
1612
# v4
1713
- name: Checkout
1814
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
15+
with:
16+
ref: ${{ github.event.pull_request.head.sha }}
1917

2018
- name: Setup Node
2119
uses: ./.github/actions/composite/setupNode
2220

23-
- name: Run generateTranslations dry run
24-
run: npx ts-node ./scripts/generateTranslations.ts --dry-run
21+
- name: Check if English translations were modified
22+
id: check-en-changes
23+
run: |
24+
if [[ "${{ github.event.action }}" == "opened" ]]; then
25+
# For newly opened PRs, check if en.ts was changed in the entire PR
26+
echo "🆕 PR was just opened - checking entire PR for en.ts changes"
27+
if gh pr diff ${{ github.event.pull_request.number }} --name-only | grep -q "^src/languages/en\.ts$"; then
28+
echo "EN_CHANGED=true" >> "$GITHUB_OUTPUT"
29+
echo "✅ English translations were modified in this PR - will generate translations"
30+
else
31+
echo "EN_CHANGED=false" >> "$GITHUB_OUTPUT"
32+
echo "⏭️ English translations were not modified in this PR - skipping translation generation"
33+
fi
34+
else
35+
# For synchronize events, only check the new commits that were just pushed
36+
echo "🔄 PR was updated - checking only the new commits for en.ts changes"
37+
git fetch --no-tags --depth=1 --no-recurse-submodules origin ${{ github.event.before }}
38+
if git diff --name-only ${{ github.event.before }} ${{ github.event.after }} | grep -q "^src/languages/en\.ts$"; then
39+
echo "EN_CHANGED=true" >> "$GITHUB_OUTPUT"
40+
echo "✅ English translations were modified in the new commits - will generate translations"
41+
else
42+
echo "EN_CHANGED=false" >> "$GITHUB_OUTPUT"
43+
echo "⏭️ English translations were not modified in the new commits - skipping translation generation"
44+
fi
45+
fi
46+
env:
47+
GITHUB_TOKEN: ${{ github.token }}
48+
49+
- name: Fetch base ref
50+
if: steps.check-en-changes.outputs.EN_CHANGED == 'true'
51+
run: git fetch --no-tags --depth=1 --no-recurse-submodules origin +refs/heads/${{ github.event.pull_request.base.ref }}:refs/heads/${{ github.event.pull_request.base.ref }}
52+
53+
- name: Run generateTranslations for added translations
54+
if: steps.check-en-changes.outputs.EN_CHANGED == 'true'
55+
run: npx ts-node ./scripts/generateTranslations.ts --verbose --compare-ref=${{ github.event.pull_request.base.ref }}
56+
env:
57+
GITHUB_TOKEN: ${{ github.token }}
58+
OPENAI_API_KEY: ${{ secrets.PROPOSAL_POLICE_API_KEY }}
59+
60+
- name: Check if there are any changes after running generateTranslations
61+
if: steps.check-en-changes.outputs.EN_CHANGED == 'true'
62+
id: checkDiff
63+
run: |
64+
if git diff --quiet HEAD; then
65+
echo "✅ No changes detected"
66+
echo "HAS_DIFF=false" >> "$GITHUB_OUTPUT"
67+
else
68+
echo "🦜 Polyglot Parrot detected changes! 🦜"
69+
echo "HAS_DIFF=true" >> "$GITHUB_OUTPUT"
70+
fi
71+
72+
- name: Hide any existing Polyglot Parrot comments
73+
continue-on-error: true
74+
if: steps.check-en-changes.outputs.EN_CHANGED == 'true' && steps.checkDiff.outputs.HAS_DIFF == 'true'
75+
run: |
76+
EXISTING_COMMENTS="$(gh pr view ${{ github.event.pull_request.number }} --json comments --jq '.comments[] | select(.body | startswith("## 🦜 Polyglot Parrot! 🦜")) | .id')"
77+
if [[ -z "$EXISTING_COMMENTS" ]]; then
78+
echo "🦗 No existing Polyglot Parrot comments found"
79+
else
80+
echo "Found existing Polyglot Parrot comment(s), hiding as outdated..."
81+
echo "$EXISTING_COMMENTS" | while read -r comment_id; do
82+
# shellcheck disable=SC2016
83+
gh api graphql -f query='
84+
mutation($commentId: ID!) {
85+
minimizeComment(input: {
86+
subjectId: $commentId,
87+
classifier: OUTDATED
88+
}) {
89+
minimizedComment {
90+
isMinimized
91+
}
92+
}
93+
}' -f commentId="$comment_id" || echo "Failed to minimize comment $comment_id"
94+
done
95+
fi
96+
env:
97+
GITHUB_TOKEN: ${{ github.token }}
2598

26-
- name: Explain failure
27-
if: failure()
99+
- name: Post diff in PR as a comment
100+
if: steps.check-en-changes.outputs.EN_CHANGED == 'true' && steps.checkDiff.outputs.HAS_DIFF == 'true'
28101
run: |
29-
echo '::error:: 😦 Something you did broke scripts/generateTranslations.ts. Most likely, this means you added an import that caused react-native to be directly or indirectly imported into the script.'
30-
exit 1
102+
# Create temp files and set up cleanup
103+
TEMP_COMMENT_FILE=$(mktemp)
104+
TEMP_PATCH_FILE=$(mktemp --suffix=.patch)
105+
trap 'rm -f "$TEMP_COMMENT_FILE" "$TEMP_PATCH_FILE"' EXIT
106+
107+
# Generate the diff and save to patch file
108+
git diff HEAD > "$TEMP_PATCH_FILE"
109+
110+
# Ensure the patch ends with a newline to prevent "corrupt patch" errors
111+
echo "" >> "$TEMP_PATCH_FILE"
112+
113+
# Check file size (65536 chars is ~64KB, let's use 60KB as safety margin)
114+
PATCH_SIZE=$(wc -c < "$TEMP_PATCH_FILE")
115+
readonly MAX_COMMENT_SIZE=61440 # 60KB in bytes
116+
117+
readonly PARROT_HEADER="## 🦜 Polyglot Parrot! 🦜"
118+
readonly PARROT_INTRO="_Squawk!_ Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues:"
119+
PARROT_FOOTER="$(cat <<'EOF'
120+
> [!NOTE]
121+
> You can apply these changes to your branch by copying the patch to your clipboard, then running `pbpaste | git apply` 😉
122+
EOF
123+
)"
124+
readonly PARROT_FOOTER
125+
126+
if [ "$PATCH_SIZE" -le "$MAX_COMMENT_SIZE" ]; then
127+
# Small diff - include in comment
128+
{
129+
echo "$PARROT_HEADER"
130+
echo
131+
echo "$PARROT_INTRO"
132+
echo
133+
echo '```diff'
134+
cat "$TEMP_PATCH_FILE"
135+
echo '```'
136+
echo
137+
echo "$PARROT_FOOTER"
138+
} > "$TEMP_COMMENT_FILE"
139+
else
140+
# Large diff - upload as gist and link
141+
echo "📁 Creating gist for large diff..."
142+
GIST_URL=$(gh gist create "$TEMP_PATCH_FILE" --desc "🦜 Polyglot Parrot translations for PR #${{ github.event.pull_request.number }}" --filename "translations.patch")
143+
{
144+
echo "$PARROT_HEADER"
145+
echo
146+
echo "$PARROT_INTRO"
147+
echo
148+
echo "The diff is too large to include in this comment _($((PATCH_SIZE / 1000))KB)_, so I've created a gist for you:"
149+
echo
150+
echo "📋 **[View the translation diff here](${GIST_URL})** 📋"
151+
echo
152+
echo "$PARROT_FOOTER"
153+
} > "$TEMP_COMMENT_FILE"
154+
fi
155+
156+
# Post comment using the temp file
157+
gh pr comment ${{ github.event.pull_request.number }} --body-file "$TEMP_COMMENT_FILE"
158+
env:
159+
# Use OS_BOTIFY_TOKEN so that we can create a gist
160+
GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }}

.github/workflows/test.yml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,42 @@ jobs:
4343
key: ${{ runner.os }}-jest
4444

4545
- name: Jest tests
46-
run: NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules" npm test -- --silent --shard=${{ fromJSON(matrix.chunk) }}/${{ strategy.job-total }} --max-workers ${{ steps.cpu-cores.outputs.count }}
46+
run: NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules" npm test -- --silent --shard=${{ fromJSON(matrix.chunk) }}/${{ strategy.job-total }} --max-workers ${{ steps.cpu-cores.outputs.count }} --coverage --coverageDirectory=coverage/shard-${{ matrix.chunk }}
47+
48+
- name: Upload coverage to Codecov (PRs - tokenless)
49+
if: ${{ github.event_name == 'pull_request' }}
50+
# v5
51+
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7
52+
with:
53+
files: coverage/shard-${{ matrix.chunk }}/coverage-final.json,coverage/shard-${{ matrix.chunk }}/lcov.info
54+
flags: unit,shard-${{ matrix.chunk }}
55+
name: jest-${{ matrix.chunk }}
56+
disable_search: true
57+
fail_ci_if_error: true
58+
59+
- name: Load Codecov token from 1Password
60+
id: load-codecov-token
61+
# Codecov needs a token to be run on the default branch (which happens in preDeploy.yml after a PR is merged to main)
62+
if: ${{ github.event_name == 'workflow_call' && github.ref == 'refs/heads/main' }}
63+
# v2
64+
uses: 1password/load-secrets-action@581a835fb51b8e7ec56b71cf2ffddd7e68bb25e0
65+
with:
66+
export-env: false
67+
env:
68+
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
69+
CODECOV_TOKEN: op://${{ vars.OP_VAULT }}//CodeCov-API/key
70+
71+
- name: Upload coverage to Codecov (token required)
72+
if: ${{ github.event_name == 'workflow_call' && github.ref == 'refs/heads/main' }}
73+
# v5
74+
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7
75+
with:
76+
token: ${{ steps.load-codecov-token.outputs.CODECOV_TOKEN }}
77+
files: coverage/shard-${{ matrix.chunk }}/coverage-final.json,coverage/shard-${{ matrix.chunk }}/lcov.info
78+
flags: unit,shard-${{ matrix.chunk }}
79+
name: jest-${{ matrix.chunk }}
80+
disable_search: true
81+
fail_ci_if_error: true
4782

4883
storybookTests:
4984
if: ${{ github.event.head_commit.author.name != 'OSBotify' && github.event.head_commit.author.name != 'imgbot[bot]' || github.event_name == 'workflow_call' }}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
on:
2+
pull_request:
3+
types: [opened, synchronize]
4+
paths-ignore: ['docs', 'help']
5+
6+
jobs:
7+
# We always run dry-run the script to verify that it still works.
8+
# The generateTranslations script runs with ts-node, which can't handle Flow
9+
# (the specialized JS that React Native is written in).
10+
# Therefore, adding an import in the wrong place could break the script, even if you didn't modify the script.
11+
dryRun:
12+
runs-on: ubuntu-latest
13+
steps:
14+
# v4
15+
- name: Checkout
16+
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
17+
18+
- name: Setup Node
19+
uses: ./.github/actions/composite/setupNode
20+
21+
- name: Run generateTranslations dry run
22+
run: npx ts-node ./scripts/generateTranslations.ts --dry-run
23+
24+
- name: Explain failure
25+
if: failure()
26+
run: |
27+
echo '::error:: 😦 Something you did broke scripts/generateTranslations.ts. Most likely, this means you added an import that caused react-native to be directly or indirectly imported into the script.'
28+
exit 1

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
20.19.3
1+
20.19.4

.storybook/preview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import React from 'react';
33
import Onyx from 'react-native-onyx';
44
import {SafeAreaProvider} from 'react-native-safe-area-context';
55
import type {Parameters} from 'storybook/internal/types';
6+
import {EnvironmentProvider} from '@components/EnvironmentContext';
67
import OnyxListItemProvider from '@components/OnyxListItemProvider';
78
import {SearchContextProvider} from '@components/Search/SearchContext';
89
import ComposeProviders from '@src/components/ComposeProviders';
910
import HTMLEngineProvider from '@src/components/HTMLEngineProvider';
1011
import {LocaleContextProvider} from '@src/components/LocaleContextProvider';
11-
import {EnvironmentProvider} from '@src/components/withEnvironment';
1212
import {KeyboardStateProvider} from '@src/components/withKeyboardState';
1313
import CONST from '@src/CONST';
1414
import IntlStore from '@src/languages/IntlStore';

Mobile-Expensify

README.md

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -586,30 +586,9 @@ If you seek some additional information you can always refer to the [extended ve
586586

587587
# Philosophy
588588
This application is built with the following principles.
589-
1. **Data Flow** - Ideally, this is how data flows through the app:
590-
1. Server pushes data to the disk of any client (Server -> Pusher event -> Action listening to pusher event -> Onyx).
591-
>**Note:** Currently the code only does this with report comments. Until we make more server changes, this step is actually done by the client requesting data from the server via XHR and then storing the response in Onyx.
592-
2. Disk pushes data to the UI (Onyx -> withOnyx() -> React component).
593-
3. UI pushes data to people's brains (React component -> device screen).
594-
4. Brain pushes data into UI inputs (Device input -> React component).
595-
5. UI inputs push data to the server (React component -> Action -> XHR to server).
596-
6. Go to 1
597-
![New Expensify Data Flow Chart](/contributingGuides/data_flow.png)
598-
1. **Offline first**
599-
- Be sure to read [OFFLINE.md](contributingGuides/philosophies/OFFLINE.md)!
600-
- All data that is brought into the app and is necessary to display the app when offline should be stored on disk in persistent storage (eg. localStorage on browser platforms). [AsyncStorage](https://reactnative.dev/docs/asyncstorage) is a cross-platform abstraction layer that is used to access persistent storage.
601-
- All data that is displayed, comes from persistent storage.
602-
1. **UI Binds to data on disk**
603-
- Onyx is a Pub/Sub library to connect the application to the data stored on disk.
604-
- UI components subscribe to Onyx (using `withOnyx()`) and any change to the Onyx data is published to the component by calling `setState()` with the changed data.
605-
- Libraries subscribe to Onyx (with `Onyx.connect()`) and any change to the Onyx data is published to the callback with the changed data.
606-
- The UI should never call any Onyx methods except for `Onyx.connect()`. That is the job of Actions (see next section).
607-
- The UI always triggers an Action when something needs to happen (eg. a person inputs data, the UI triggers an Action with this data).
608-
- The UI should be as flexible as possible when it comes to:
609-
- Incomplete or missing data. Always assume data is incomplete or not there. For example, when a comment is pushed to the client from a pusher event, it's possible that Onyx does not have data for that report yet. That's OK. A partial report object is added to Onyx for the report key `report_1234 = {reportID: 1234, isUnread: true}`. Then there is code that monitors Onyx for reports with incomplete data, and calls `openReport(1234)` to get the full data for that report. The UI should be able to gracefully handle the report object not being complete. In this example, the sidebar wouldn't display any report that does not have a report name.
610-
- The order that actions are done in. All actions should be done in parallel instead of sequence.
611-
- Parallel actions are asynchronous methods that don't return promises. Any number of these actions can be called at one time and it doesn't matter what order they happen in or when they complete.
612-
- In-Sequence actions are asynchronous methods that return promises. This is necessary when one asynchronous method depends on the results from a previous asynchronous method. Example: Making an XHR to `command=CreateChatReport` which returns a reportID which is used to call `command=Get&rvl=reportStuff`.
589+
1. [Data Flow](contributingGuides/philosophies/DATA-FLOW.md)
590+
1. [Offline First](contributingGuides/philosophies/OFFLINE.md)
591+
1. [Data Binding](contributingGuides/philosophies/DATA_BINDING.md)
613592
1. **Actions manage Onyx Data**
614593
- When data needs to be written to or read from the server, this is done through Actions only.
615594
- Action methods should only have return values (data or a promise) if they are called by other actions. This is done to encourage that action methods can be called in parallel with no dependency on other methods (see discussion above).
@@ -624,8 +603,7 @@ This application is built with the following principles.
624603
4. server responds
625604
5. UI updates with data from the server
626605

627-
1. **Cross Platform 99.9999%**
628-
See details [here](contributingGuides/philosophies/CROSS-PLATFORM.md).
606+
1. [Cross Platform](contributingGuides/philosophies/CROSS-PLATFORM.md).
629607

630608
----
631609

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 1009021500
118-
versionName "9.2.15-0"
117+
versionCode 1009021512
118+
versionName "9.2.15-12"
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"

babel.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const defaultPluginsForWebpack = [
4646

4747
// Keep it last
4848
'react-native-reanimated/plugin',
49+
'@babel/plugin-transform-export-namespace-from',
4950
];
5051

5152
// The Fullstory annotate plugin generated a few errors when executed in Electron. Let's
@@ -133,6 +134,7 @@ const metro = {
133134
},
134135
},
135136
],
137+
'@babel/plugin-transform-export-namespace-from',
136138
],
137139
env: {
138140
production: {

0 commit comments

Comments
 (0)