Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3b0f785
chore: day one strings
ErikSin Mar 2, 2026
5acf9ff
chore: more strings
ErikSin Mar 2, 2026
93e81fd
chore: more string
ErikSin Mar 3, 2026
4c2cbed
chore: more strings
ErikSin Mar 4, 2026
99f85f2
Starts to marking primaary strings.
cimigree Apr 14, 2026
7fa8e51
Messages with primary strings.
cimigree Apr 14, 2026
124677b
Adds markers for the rest of the primary strings.
cimigree Apr 14, 2026
e6b4865
Rest of messages for the new primary ids.
cimigree Apr 14, 2026
3efe893
Adds new extraction script to sort messages into primary and second f…
cimigree Apr 14, 2026
3292ea8
Temporary patch for build translations to ignore file folders so CI c…
cimigree Apr 14, 2026
7c62e1b
Comments out language test since it is broken for now as there is a k…
cimigree Apr 15, 2026
6431d84
Removes comments about primary string.
cimigree Apr 15, 2026
ddd304e
Updates yaml for crowdin to include folder destinations.
cimigree Apr 15, 2026
2e4c1ad
Removes english from german and french files.
cimigree Apr 15, 2026
0e66acc
Removes English from translated files.
cimigree Apr 15, 2026
6572ea8
Adds folders for all of the languages divided into primary and second…
cimigree Apr 15, 2026
b663878
Deletes unusued flat files. Adds folders for custom languages not alr…
cimigree Apr 15, 2026
fa683f9
Fixes some translations. Removes English. Adds delete an observation …
cimigree Apr 16, 2026
590377a
Tries to match trailing new line to crowdin.
cimigree Apr 16, 2026
c39982d
Some french translations.
cimigree Apr 16, 2026
c6d2212
chore: update script
ErikSin Apr 16, 2026
e79cccb
New Crowdin updates (#1847)
digidem-bot Apr 16, 2026
d5bfab4
chore: update script to remove regional tag
ErikSin Apr 16, 2026
f6fdb83
chore: update languages.json
ErikSin Apr 16, 2026
5c07601
chore: makes all translations available
ErikSin Apr 16, 2026
844df33
chore: remove en.json
ErikSin Apr 16, 2026
09fe2b2
chore: add coment explaining
ErikSin Apr 16, 2026
1dd5797
Merge pull request #1849 from digidem/chore-update-translations-scrip…
ErikSin Apr 16, 2026
b96eaa8
Revert "Comments out language test since it is broken for now as ther…
ErikSin Apr 16, 2026
b0b2fcb
Merge pull request #1844 from digidem/chore/translations-new-structure
ErikSin Apr 16, 2026
16b178f
Merge branch 'develop' into feature/mark-primary-strings
ErikSin Apr 16, 2026
cd75867
chore: 2 new translations
ErikSin Apr 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
909 changes: 909 additions & 0 deletions messages/en-US/primary.json

Large diffs are not rendered by default.

1,350 changes: 1,350 additions & 0 deletions messages/en-US/secondary.json

Large diffs are not rendered by default.

1,854 changes: 938 additions & 916 deletions messages/en.json
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should delete this file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build-translations.mjs script still reads from the messages/ folder and en.json is in there. So if we delete it now, the app's English strings would break until step 5. Are you sure we want to do that?

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"build:translations": "node ./scripts/build-translations.mjs",
"build:intl-polyfills": "node ./scripts/build-intl-polyfills.mjs",
"build:backend": "node ./scripts/build-backend.mjs",
"extract-messages": "formatjs extract 'src/frontend/**/*.{ts,tsx}' --ignore='**/*.d.ts' --format crowdin --out-file ./messages/en.json",
"extract-messages": "node scripts/extract-messages.mjs",
"eas-build-post-install": "npm run build:backend && npm run build:translations && npm run build:intl-polyfills",
"eas-build-on-success": "scripts/upload-release.sh",
"upgrade-deps:all": "npm-check --update --save-exact",
Expand Down
12 changes: 7 additions & 5 deletions scripts/build-translations.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ async function loadMessages() {

/** @type {Array<[string, any]>} */
const loadedMessages = await Promise.all(
files.map(async file => {
const lang = path.parse(file.name).name;
const msgs = JSON.parse(await readFile(path.join(messagesDir, file.name)));
return [lang, msgs];
}),
files
.filter(file => file.isFile())
.map(async file => {
const lang = path.parse(file.name).name;
const msgs = JSON.parse(await readFile(path.join(messagesDir, file.name)));
return [lang, msgs];
}),
);

const result = {};
Expand Down
53 changes: 53 additions & 0 deletions scripts/extract-messages.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env node

/**
* Extracts messages from source files and splits them into two files:
* messages/en-US/primary.json — strings marked as primary (id starts with "$1")
* messages/en-US/secondary.json — all other strings
*
* Primary strings are the ones that will be prioritized for translation in Crowdin.
*/

import path from 'node:path';
import fs from 'node:fs';
import {writeFile} from 'node:fs/promises';
import {execSync} from 'node:child_process';

const PROJECT_ROOT = new URL('../', import.meta.url).pathname;
const OUT_DIR = path.join(PROJECT_ROOT, 'messages', 'en-US');
const TEMP_FILE = path.join(OUT_DIR, '_all.json');
const PRIMARY_FILE = path.join(OUT_DIR, 'primary.json');
const SECONDARY_FILE = path.join(OUT_DIR, 'secondary.json');

fs.mkdirSync(OUT_DIR, {recursive: true});

// Extract all messages into a temporary combined file using the crowdin format
execSync(
`npx formatjs extract 'src/frontend/**/*.{ts,tsx}' --ignore='**/*.d.ts' --format crowdin --out-file '${TEMP_FILE}'`,
{cwd: PROJECT_ROOT, stdio: 'inherit'},
);

const all = JSON.parse(fs.readFileSync(TEMP_FILE, 'utf8'));

const primary = {};
const secondary = {};

for (const [key, value] of Object.entries(all)) {
if (key.startsWith('$1')) {
primary[key] = value;
} else {
secondary[key] = value;
}
}

fs.unlinkSync(TEMP_FILE);

await writeFile(PRIMARY_FILE, JSON.stringify(primary, null, 2));
await writeFile(SECONDARY_FILE, JSON.stringify(secondary, null, 2));

console.log(
`Extracted ${Object.keys(primary).length} primary strings → messages/en-US/primary.json`,
);
console.log(
`Extracted ${Object.keys(secondary).length} secondary strings → messages/en-US/secondary.json`,
);
6 changes: 4 additions & 2 deletions src/frontend/screens/AllProjects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import {BottomSheetWrapper} from '../sharedComponents/BottomSheetWrapper';
import CheckMark from '../images/CheckMark.svg';

const m = defineMessages({
// primary-string
newCollab: {
id: 'allProjects.newCollab',
id: '$1allProjects.newCollab',
defaultMessage: 'New Collaboration',
},
// primary-string
close: {
id: 'allProjects.close',
id: '$1allProjects.close',
defaultMessage: 'Close',
},
});
Expand Down
12 changes: 6 additions & 6 deletions src/frontend/screens/AppPasscode/ConfirmPasscodeSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,28 @@ import {HeaderText} from '../../sharedComponents/Text/HeaderText';
import {BodyText} from '../../sharedComponents/Text/BodyText';
import {PrimaryButton, SecondaryButton} from '../../sharedComponents/Buttons';
import {toError} from '../../utils/errors';

// primary-string ENTIRE SCREEN
const m = defineMessages({
title: {
id: 'screens.AppPasscode.ConfirmPasscodeSheet.title',
id: '$1screens.AppPasscode.ConfirmPasscodeSheet.title',
defaultMessage:
'App Passcodes can never be recovered if lost or forgotten!',
},
suggestion: {
id: 'screens.AppPasscode.ConfirmPasscodeSheet.suggestion',
id: '$1screens.AppPasscode.ConfirmPasscodeSheet.suggestion',
defaultMessage:
'Make sure to note your passcode in a secure location before saving.',
},
cancel: {
id: 'screens.AppPasscode.ConfirmPasscodeSheet.cancel',
id: '$1screens.AppPasscode.ConfirmPasscodeSheet.cancel',
defaultMessage: 'Cancel',
},
saveAppPasscode: {
id: 'screens.AppPasscode.ConfirmPasscodeSheet.saveAppPasscode',
id: '$1screens.AppPasscode.ConfirmPasscodeSheet.saveAppPasscode',
defaultMessage: 'Save App Passcode',
},
passcode: {
id: 'screens.AppPasscode.ConfirmPasscodeSheet.passcode',
id: '$1screens.AppPasscode.ConfirmPasscodeSheet.passcode',
defaultMessage: 'Passcode: {passcode}',
description: 'used to indicate to the user what the new passcode will be.',
},
Expand Down
15 changes: 8 additions & 7 deletions src/frontend/screens/AppPasscode/SetPasscode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,34 @@ import {OBSCURE_PASSCODE} from '../../constants';
import {NativeNavigationComponent} from '../../sharedTypes/navigation';
import {InputPasscode} from './InputPasscode';

// primary-string ENTIRE SCREEN
const m = defineMessages({
titleSet: {
id: 'screens.AppPasscode.NewPasscode.InputPasscodeScreen.TitleSet',
id: '$1screens.AppPasscode.NewPasscode.InputPasscodeScreen.TitleSet',
defaultMessage: 'Set App Passcode',
},
initialPassError: {
id: 'screens.AppPasscode.NewPasscode.InputPasscodeScreen.initialPassError',
id: '$1screens.AppPasscode.NewPasscode.InputPasscodeScreen.initialPassError',
defaultMessage: 'Password must be 5 numbers',
},
titleConfirm: {
id: 'screens.AppPasscode.NewPasscode.InputPasscodeScreen.TitleConfirm',
id: '$1screens.AppPasscode.NewPasscode.InputPasscodeScreen.TitleConfirm',
defaultMessage: 'Re-enter Passcode',
},
subTitleSet: {
id: 'screens.AppPasscode.NewPasscode.InputPasscodeScreen.subTitleSet',
id: '$1screens.AppPasscode.NewPasscode.InputPasscodeScreen.subTitleSet',
defaultMessage: 'This passcode will be required to open the CoMapeo App',
},
passwordDoesNotMatch: {
id: 'screens.AppPasscode.NewPasscode.InputPasscodeScreen.passwordDoesNotMatch',
id: '$1screens.AppPasscode.NewPasscode.InputPasscodeScreen.passwordDoesNotMatch',
defaultMessage: 'Password does not match',
},
obscurePasscodeError: {
id: 'screens.AppPasscode.NewPasscode.InputPasscodeScreen.obscurePasscodeError',
id: '$1screens.AppPasscode.NewPasscode.InputPasscodeScreen.obscurePasscodeError',
defaultMessage: 'Cannot be used as a Passcode',
},
title: {
id: 'screens.AppPasscode.NewPasscode.SetPasscodeScreen.title',
id: '$1screens.AppPasscode.NewPasscode.SetPasscodeScreen.title',
defaultMessage: 'Set Passcode',
},
});
Expand Down
11 changes: 6 additions & 5 deletions src/frontend/screens/AppPasscode/TurnOffPasscode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,28 @@ import {HeaderBackButtonProps} from '@react-navigation/elements';
import {BodyText} from '../../sharedComponents/Text/BodyText';
import {useSecurityState} from '../../contexts/SecurityStoreContext';

// primary-string ENTIRE SCREEN
const m = defineMessages({
appUsePasscode: {
id: 'screens.AppPasscode.TurnOffPasscode.usePasscode',
id: '$1screens.AppPasscode.TurnOffPasscode.usePasscode',
defaultMessage: 'Use App Passcode',
},
changePasscode: {
id: 'screens.AppPasscode.TurnOffPasscode.changePasscode',
id: '$1screens.AppPasscode.TurnOffPasscode.changePasscode',
defaultMessage: 'Change App Passcode',
},
description: {
id: 'screens.AppPasscode.TurnOffPasscode.description',
id: '$1screens.AppPasscode.TurnOffPasscode.description',
defaultMessage:
'App Passcode adds an additional layer of security by requiring that you enter a passcode in order to open the CoMapeo app.',
},
currentlyUsing: {
id: 'screens.AppPasscode.TurnOffPasscode.currentlyUsing',
id: '$1screens.AppPasscode.TurnOffPasscode.currentlyUsing',
defaultMessage:
'You are currently using App Passcode. See below to stop using or change your passcode.',
},
title: {
id: 'screens.AppPasscode.TurnOffPasscode.title',
id: '$1screens.AppPasscode.TurnOffPasscode.title',
defaultMessage: 'App Passcode',
},
});
Expand Down
11 changes: 6 additions & 5 deletions src/frontend/screens/AppPasscode/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,28 @@ import {PrimaryButton} from '../../sharedComponents/Buttons';
import {HeaderText} from '../../sharedComponents/Text/HeaderText';
import {BodyText} from '../../sharedComponents/Text/BodyText';

// primary-string ENTIRE SCREEN
const m = defineMessages({
navTitle: {
id: 'screens.AppPasscode',
id: '$1screens.AppPasscode',
defaultMessage: 'App Passcode',
},
title: {
id: 'screens.AppPasscode.NewPasscode.Splash.title',
id: '$1screens.AppPasscode.NewPasscode.Splash.title',
defaultMessage: 'What is App Passcode?',
},
passcodeDescription: {
id: 'screens.AppPasscode.PasscodeIntro.description',
id: '$1screens.AppPasscode.PasscodeIntro.description',
defaultMessage:
'App Passcode allows you to add an additional layer of security by requiring that you enter a passcode in order to open the CoMapeo app. You can define your own 5-digit passcode by turning on the feature below.',
},
warning: {
id: 'screens.AppPasscode.PasscodeIntro.warning',
id: '$1screens.AppPasscode.PasscodeIntro.warning',
defaultMessage:
'<bold>Please note that forgotten passcodes cannot be recovered!</bold> Once this feature is enabled, if you forget or lose your passcode, you will not be able to open CoMapeo and will lose access to any CoMapeo data that has not been exchanged with other project participants.',
},
continue: {
id: 'screens.AppPasscode.NewPasscode.Splash.continue',
id: '$1screens.AppPasscode.NewPasscode.Splash.continue',
defaultMessage: 'Continue',
},
});
Expand Down
9 changes: 6 additions & 3 deletions src/frontend/screens/Audio/AudioAskPermissionBottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import {useAudioPermissionModalMutation} from '../../hooks/useAudioPermissionTra
import {useSecurityState} from '../../contexts/SecurityStoreContext';

const m = defineMessages({
// primary-string
title: {
id: 'screens.AudioPermission.title',
id: '$1screens.AudioPermission.title',
defaultMessage: 'Recording Audio with CoMapeo',
description: 'Screen title for audio permission screen',
},
Expand All @@ -28,13 +29,15 @@ const m = defineMessages({
'To record audio while using the app and in the background CoMapeo needs to access your microphone. Please enable microphone permissions in your app settings.',
description: 'Screen description for audio permission screen',
},
// primary-string
notNowButtonText: {
id: 'screens.AudioPermission.Button.notNow',
id: '$1screens.AudioPermission.Button.notNow',
defaultMessage: 'Not Now',
description: 'Screen button text for not granting audio permission',
},
// primary-string
allowButtonText: {
id: 'screens.AudioPermission.Button.allow',
id: '$1screens.AudioPermission.Button.allow',
defaultMessage: 'Allow',
description: 'Screen button text for granting the audio permission',
},
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/screens/Audio/AudioAttachmentPlaybackScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ const m = defineMessages({
id: 'screens.AudioAttachmentPlayback.navTitle',
defaultMessage: 'Audio Recording',
},
// primary-string
share: {
id: 'screens.AudioAttachmentPlayback.share',
id: '$1screens.AudioAttachmentPlayback.share',
defaultMessage: 'Share',
},
});
Expand Down
9 changes: 6 additions & 3 deletions src/frontend/screens/Audio/AudioDraftPlaybackScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,19 @@ import * as Sentry from '@sentry/react-native';
import {toError} from '../../utils/errors';

const m = defineMessages({
// primary-string
recordingSaved: {
id: 'screens.AudioPlaybackNew.recordingSaved',
id: '$1screens.AudioPlaybackNew.recordingSaved',
defaultMessage: 'Recording Saved!',
},
// primary-string
backToEditing: {
id: 'screens.AudioPlaybackNew.backToEditing',
id: '$1screens.AudioPlaybackNew.backToEditing',
defaultMessage: 'Back to Editing',
},
// primary-string
delete: {
id: 'screens.AudioPlaybackNew.delete',
id: '$1screens.AudioPlaybackNew.delete',
defaultMessage: 'Delete',
},
});
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/screens/Audio/AudioRecording/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ const m = defineMessages({
id: 'screens.AudioRecording.lessThan1',
defaultMessage: 'Less than a minute left',
},
// primary-string
nowRecording: {
id: 'screens.AudioRecording.nowRecording',
id: '$1screens.AudioRecording.nowRecording',
defaultMessage: 'Now Recording...',
},
});
Expand Down
16 changes: 10 additions & 6 deletions src/frontend/screens/BackgroundMaps/BackgroundMaps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ import {DownloadIcon} from '../../sharedComponents/icons';
import {useMutation} from '@tanstack/react-query';

const m = defineMessages({
// primary-string
screenTitle: {
id: 'screens.Settings.MapManagement.BackgroundMaps.screenTitle',
id: '$1screens.Settings.MapManagement.BackgroundMaps.screenTitle',
defaultMessage: 'Background Map',
},
description1: {
Expand Down Expand Up @@ -93,21 +94,23 @@ const m = defineMessages({
id: 'screens.Settings.MapManagement.BackgroundMaps.deleteMapButtonText',
defaultMessage: 'Delete Map',
},

// primary-string
importErrorTitle: {
id: 'screens.Settings.MapManagement.BackgroundMaps.importErrorTitle',
id: '$1screens.Settings.MapManagement.BackgroundMaps.importErrorTitle',
defaultMessage: 'Import Error',
},
importErrorDesciption: {
id: 'screens.Settings.MapManagement.BackgroundMaps.importErrorDescription',
defaultMessage: 'Unable to import the file. Please go back and try again.',
},
// primary-string
sendMap: {
id: 'screens.Settings.MapManagement.BackgroundMaps.sendMap',
id: '$1screens.Settings.MapManagement.BackgroundMaps.sendMap',
defaultMessage: 'Send Map',
},
// primary-string
removeMap: {
id: 'screens.Settings.MapManagement.BackgroundMaps.removeMap',
id: '$1screens.Settings.MapManagement.BackgroundMaps.removeMap',
defaultMessage: 'Remove Map',
},
addedOn: {
Expand All @@ -118,8 +121,9 @@ const m = defineMessages({
id: 'screens.Settings.MapManagement.BackgroundMaps.megabytes',
defaultMessage: '{size} MB',
},
// primary-string
chooseFile: {
id: 'screens.Settings.MapManagement.BackgroundMaps.chooseFile',
id: '$1screens.Settings.MapManagement.BackgroundMaps.chooseFile',
defaultMessage: 'Choose File',
},
acceptedFileTypes: {
Expand Down
Loading
Loading