Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 12 additions & 11 deletions .cursor/rules/localization-takeaways.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ alwaysApply: true
`<target/>` for new English units; add a `<context-group>` with
`context-type="sourcefile"` set to the primary consuming file (e.g.
`BurritoFormat.tsx`) so translators see where the string is used.
- **Regenerate TypeScript and default English:** after XLIFF changes, run the
updater from [**`localization\bin\Debug`**](localization/bin/Debug) as the
**current working directory** — not the repo root. The tool resolves inputs as
`..\..\…` relative to that folder (see [`localization/Program.cs`](localization/Program.cs));
starting it elsewhere makes it look for `TranscriberAdmin-en-1.2.xliff` in the
wrong place and fails with `FileNotFoundException`. Convenience:
[`localization/Updatestrings.bat`](localization/Updatestrings.bat) does
`cd bin\debug` then runs the exe.
- **Regenerate TypeScript and default English:** after XLIFF changes, from the
**repo root** in **PowerShell**, run:
`cd .\localization\bin\Debug ; &.\.\updateLocalization.exe`
The tool expects that working directory — not the repo root as cwd for the exe
— because it resolves inputs relative to that folder (see
[`localization/Program.cs`](localization/Program.cs)); starting it elsewhere
makes it look for `TranscriberAdmin-en-1.2.xliff` in the wrong place and fails
with `FileNotFoundException`. Optional: [`localization/Updatestrings.bat`](localization/Updatestrings.bat)
(`cd bin\debug` then the exe) if you prefer cmd.
- **What the pipeline writes:** the exe generates `localizeModel.tsx` and
`localizationReducer.tsx` under `localization\`, then
[`localization/UpdateLocalizationFollowUp.bat`](localization/UpdateLocalizationFollowUp.bat)
Expand All @@ -43,8 +44,8 @@ alwaysApply: true
selectors fit, add a new entry in `selectors.tsx` that calls
`localStrings(state, { layout: 'yourSection' })`, matching the section name
the tool emits in `model.tsx` / Redux `strings` slice. Add the corresponding
groups and units in XLIFF, then run the updater from `localization\bin\Debug`
as above.
groups and units in XLIFF, then run the updater with the PowerShell one-liner
above (cwd `localization\bin\Debug`).
- **Verify after regen:** run `npm run typecheck` from `src\renderer` (or root
`npm run typecheck:web`, which delegates there).

Expand All @@ -53,7 +54,7 @@ alwaysApply: true
| Step | Action |
| ---- | -------------------------------------------------------------------------------------------------------------- |
| 1 | Add/update `<trans-unit>` in `localization/TranscriberAdmin-en-1.2.xliff` |
| 2 | `cd localization\bin\Debug` → run `updateLocalization.exe`, **or** run `Updatestrings.bat` from `localization` |
| 2 | From repo root (PowerShell): `cd .\localization\bin\Debug ; &.\.\updateLocalization.exe`**or** `Updatestrings.bat` from `localization` (cmd) |
| 3 | Confirm `model.tsx` / `reducers.tsx` (and JSON if applicable) updated under `src/renderer/…` |
| 4 | `cd src\renderer` → `npm run typecheck` |
| 5 | Use `useSelector(yourSelector)` (add a selector only if the section is new) |
17 changes: 12 additions & 5 deletions localization/TranscriberAdmin-en-1.2.xliff
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-strict.xsd" version="1.2">
<file original="TranscriberAdmin-en.xliff" source-language="en" target-language="en" datatype="plaintext">
<body>
Expand Down Expand Up @@ -7243,15 +7243,15 @@
<context context-type="sourcefile">PlanSheet.tsx</context>
</context-group>
</trans-unit>
<trans-unit id="planSheet.goToReferencePlaceholder">
<source>Verse reference, M1 S1 (if shown), section.passage (example: 2.3), or exact reference text</source>
<trans-unit id="planSheet.goToReferenceClear">
<source>Clear</source>
<target/>
<context-group>
<context context-type="sourcefile">PlanSheet.tsx</context>
</context-group>
</trans-unit>
<trans-unit id="planSheet.goToReferenceTitle">
<source>Go to reference</source>
<trans-unit id="planSheet.goToReferenceDescription">
<source>Verse reference, M1 S1 (if shown), section.passage (example: 2.3), or exact reference text</source>
<target/>
<context-group>
<context context-type="sourcefile">PlanSheet.tsx</context>
Expand All @@ -7264,6 +7264,13 @@
<context context-type="sourcefile">PlanSheet.tsx</context>
</context-group>
</trans-unit>
<trans-unit id="planSheet.goToReferenceTitle">
<source>Go to reference</source>
<target/>
<context-group>
<context context-type="sourcefile">PlanSheet.tsx</context>
</context-group>
</trans-unit>
<trans-unit id="planSheet.hidePublishing">
<source>Hide Publishing Information</source>
<target/>
Expand Down
14 changes: 10 additions & 4 deletions localization/TranscriberAdmin-en.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -6208,15 +6208,15 @@
<target/>
</segment>
</unit>
<unit id="planSheet.goToReferencePlaceholder">
<unit id="planSheet.goToReferenceClear">
<segment>
<source>Verse reference, M1 S1 (if shown), section.passage (example: 2.3), or exact reference text</source>
<source>Clear</source>
<target/>
</segment>
</unit>
<unit id="planSheet.goToReferenceTitle">
<unit id="planSheet.goToReferenceDescription">
<segment>
<source>Go to reference</source>
<source>Verse reference, M1 S1 (if shown), section.passage (example: 2.3), or exact reference text</source>
<target/>
</segment>
</unit>
Expand All @@ -6226,6 +6226,12 @@
<target/>
</segment>
</unit>
<unit id="planSheet.goToReferenceTitle">
<segment>
<source>Go to reference</source>
<target/>
</segment>
</unit>
<unit id="planSheet.hidePublishing">
<segment>
<source>Hide Publishing Information</source>
Expand Down
1 change: 1 addition & 0 deletions src/renderer/public/localization/strings21228949.json

Large diffs are not rendered by default.

41 changes: 40 additions & 1 deletion src/renderer/src/components/Sheet/PlanSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ import {
DialogContent,
DialogTitle,
IconButton,
InputAdornment,
TextField,
Typography,
debounce,
styled,
} from '@mui/material';
import ClearIcon from '@mui/icons-material/Clear';
import SaveIcon from '@mui/icons-material/Save';
import PublishOffIcon from '@mui/icons-material/PublicOffOutlined';
import PublishOnIcon from '@mui/icons-material/PublicOutlined';
Expand Down Expand Up @@ -343,6 +346,7 @@ export function PlanSheet(props: IProps) {
const [curTop, setCurTop] = useState(0);
const [goToOpen, setGoToOpen] = useState(false);
const [goToQuery, setGoToQuery] = useState('');
const goToInputRef = useRef<HTMLInputElement>(null);
const moveUp = true;
const moveDown = false;
const moveToNewSection = true;
Expand All @@ -355,6 +359,19 @@ export function PlanSheet(props: IProps) {
() => !isPersonalTeam(org, teams) && !offlineOnly,
[org, teams, offlineOnly]
);

useEffect(() => {
if (!goToOpen) return;
const id = window.setTimeout(() => {
const el = goToInputRef.current;
if (el) {
el.focus();
el.select();
}
}, 100);
return () => clearTimeout(id);
}, [goToOpen]);

const handleSave = () => {
startSave();
};
Expand Down Expand Up @@ -765,6 +782,8 @@ export function PlanSheet(props: IProps) {
inlinePassages,
lookupBook,
scripture,
currentRowIndex0:
currentRowRef.current >= 1 ? currentRowRef.current - 1 : -1,
});

if (result.ok === false) {
Expand Down Expand Up @@ -1211,8 +1230,12 @@ export function PlanSheet(props: IProps) {
>
<DialogTitle>{t.goToReferenceTitle}</DialogTitle>
<DialogContent>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1.5 }}>
{t.goToReferenceDescription}
</Typography>
<TextField
autoFocus
inputRef={goToInputRef}
fullWidth
margin="dense"
value={goToQuery}
Expand All @@ -1223,7 +1246,23 @@ export function PlanSheet(props: IProps) {
handleGoToSubmit();
}
}}
placeholder={t.goToReferencePlaceholder}
slotProps={{
input: {
endAdornment: goToQuery ? (
<InputAdornment position="end">
<IconButton
aria-label={t.goToReferenceClear}
edge="end"
size="small"
onMouseDown={(e) => e.preventDefault()}
onClick={() => setGoToQuery('')}
>
<ClearIcon fontSize="small" />
</IconButton>
</InputAdornment>
) : undefined,
},
}}
/>
</DialogContent>
<DialogActions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,63 @@ describe('findPlanSheetRowFromReferenceQuery', () => {
expect(r).toEqual({ ok: true, rowIndex: 0 });
});

it('matches scripture query verse inside passage range (same chapter)', () => {
const rowInfo: ISheet[] = [
basePassage({ book: 'MAT', reference: '1:67-80' }),
];
const r = findPlanSheetRowFromReferenceQuery('1:68', rowInfo, {
...defaultOpts,
});
expect(r).toEqual({ ok: true, rowIndex: 0 });
});

it('prefers exact passage row when query matches single verse inside another range', () => {
const rowInfo: ISheet[] = [
basePassage({ book: 'MAT', reference: '1:68' }),
basePassage({ book: 'MAT', reference: '1:67-80' }),
];
const r = findPlanSheetRowFromReferenceQuery('1:68', rowInfo, {
...defaultOpts,
});
expect(r).toEqual({ ok: true, rowIndex: 0 });
});

it('matches scripture query overlapping cross-chapter passage range', () => {
const rowInfo: ISheet[] = [
basePassage({ book: 'MAT', reference: '1:14-2:20' }),
];
expect(
findPlanSheetRowFromReferenceQuery('1:15', rowInfo, { ...defaultOpts })
).toEqual({ ok: true, rowIndex: 0 });
expect(
findPlanSheetRowFromReferenceQuery('2:5', rowInfo, { ...defaultOpts })
).toEqual({ ok: true, rowIndex: 0 });
expect(findPlanSheetRowFromReferenceQuery('2:21', rowInfo, { ...defaultOpts })).toEqual({
ok: false,
error: 'not_found',
});
});

it('matches scripture query sub-range overlapping passage range', () => {
const rowInfo: ISheet[] = [
basePassage({ book: 'MAT', reference: '1:67-80' }),
];
const r = findPlanSheetRowFromReferenceQuery('1:68-69', rowInfo, {
...defaultOpts,
});
expect(r).toEqual({ ok: true, rowIndex: 0 });
});

it('returns not_found when query verse outside passage range', () => {
const rowInfo: ISheet[] = [
basePassage({ book: 'MAT', reference: '1:67-80' }),
];
const r = findPlanSheetRowFromReferenceQuery('1:66', rowInfo, {
...defaultOpts,
});
expect(r).toEqual({ ok: false, error: 'not_found' });
});

it('returns not_found when nothing matches', () => {
const rowInfo: ISheet[] = [basePassage({ reference: '9:9' })];
const r = findPlanSheetRowFromReferenceQuery('1:1', rowInfo, {
Expand Down Expand Up @@ -200,4 +257,81 @@ describe('findPlanSheetRowFromReferenceQuery', () => {
});
expect(r).toEqual({ ok: true, rowIndex: 0 });
});

it('finds next section by phrase in title (case-insensitive)', () => {
const rowInfo: ISheet[] = [
baseSection({ sectionSeq: 1, title: 'Alpha Section' }),
basePassage({ sectionSeq: 1, passageSeq: 1, reference: '1:1' }),
baseSection({ sectionSeq: 2, title: 'Beta Section' }),
basePassage({ sectionSeq: 2, passageSeq: 1, reference: '1:2' }),
baseSection({ sectionSeq: 3, title: 'Gamma Section' }),
basePassage({ sectionSeq: 3, passageSeq: 1, reference: '1:3' }),
];
const r = findPlanSheetRowFromReferenceQuery('gamma', rowInfo, {
...defaultOpts,
currentRowIndex0: 1,
});
expect(r).toEqual({ ok: true, rowIndex: 5 });
});

it('phrase search wraps to earlier sections after the last block', () => {
const rowInfo: ISheet[] = [
baseSection({ sectionSeq: 1, title: 'Alpha Section' }),
basePassage({ sectionSeq: 1, passageSeq: 1, reference: '1:1' }),
baseSection({ sectionSeq: 2, title: 'Beta Section' }),
basePassage({ sectionSeq: 2, passageSeq: 1, reference: '1:2' }),
];
const r = findPlanSheetRowFromReferenceQuery('alpha', rowInfo, {
...defaultOpts,
currentRowIndex0: 3,
});
expect(r).toEqual({ ok: true, rowIndex: 1 });
});

it('phrase search returns not_found when only the current section matches', () => {
const rowInfo: ISheet[] = [
baseSection({ sectionSeq: 1, title: 'OnlyHere tokenxyz' }),
basePassage({ sectionSeq: 1, passageSeq: 1, reference: '1:1' }),
baseSection({ sectionSeq: 2, title: 'Other' }),
basePassage({ sectionSeq: 2, passageSeq: 1, reference: '2:1' }),
];
const r = findPlanSheetRowFromReferenceQuery('tokenxyz', rowInfo, {
...defaultOpts,
currentRowIndex0: 1,
});
expect(r).toEqual({ ok: false, error: 'not_found' });
});

it('finds next section by phrase in passage comment', () => {
const rowInfo: ISheet[] = [
baseSection({ sectionSeq: 1, title: 'Alpha' }),
basePassage({ sectionSeq: 1, passageSeq: 1, reference: '1:1' }),
baseSection({ sectionSeq: 2, title: 'Beta' }),
basePassage({
sectionSeq: 2,
passageSeq: 1,
reference: '1:2',
comment: 'recorded live',
}),
];
const r = findPlanSheetRowFromReferenceQuery('live', rowInfo, {
...defaultOpts,
currentRowIndex0: 1,
});
expect(r).toEqual({ ok: true, rowIndex: 3 });
});

it('phrase search with currentRowIndex0 -1 uses first matching section in sheet order', () => {
const rowInfo: ISheet[] = [
baseSection({ sectionSeq: 1, title: 'Alpha' }),
basePassage({ sectionSeq: 1, passageSeq: 1, reference: '1:1' }),
baseSection({ sectionSeq: 2, title: 'Beta' }),
basePassage({ sectionSeq: 2, passageSeq: 1, reference: '1:2' }),
];
const r = findPlanSheetRowFromReferenceQuery('beta', rowInfo, {
...defaultOpts,
currentRowIndex0: -1,
});
expect(r).toEqual({ ok: true, rowIndex: 3 });
});
});
Loading
Loading