diff --git a/docs/adding-new-world-checklist.md b/docs/adding-new-world-checklist.md index d49b4162..29b0e242 100644 --- a/docs/adding-new-world-checklist.md +++ b/docs/adding-new-world-checklist.md @@ -23,7 +23,7 @@ `'All worlds should be displayed as options and only one is checked'` test in `WorldSelector.test.js` for the new world - Add entries to `messages.json` - - `.name`: the name of the world + - `UI..name`: the name of the world - `.character`: the name of the character - `.label`: the label used for the world in the Scene Background dialog diff --git a/src/ActionPanel.js b/src/ActionPanel.js index dc8acc0e..c211f0a4 100644 --- a/src/ActionPanel.js +++ b/src/ActionPanel.js @@ -15,7 +15,7 @@ import './ActionPanel.scss'; type ActionPanelProps = { focusedOptionName: ?string, - selectedCommandName: ?string, + selectedActionName: ?string, programSequence: ProgramSequence, pressedStepIndex: number, intl: IntlShape, @@ -33,7 +33,7 @@ class ActionPanel extends React.Component { this.actionPanelRef = React.createRef(); } - makeStepMessageData() { + makeAriaLabels() { const currentStep = this.props.programSequence.getProgramStepAt(this.props.pressedStepIndex); let stepNumber = this.props.pressedStepIndex + 1; @@ -42,53 +42,55 @@ class ActionPanel extends React.Component { stepNumber = cachedCurrentStepLoopData.getContainingLoopPosition(); } - let stepName = ''; + let stepActionName = ''; if (currentStep.block === 'startLoop' || currentStep.block === 'endLoop') { - stepName = this.props.intl.formatMessage( + stepActionName = this.props.intl.formatMessage( { id: 'Command.loop.label' }, { loopLabel: currentStep.label } ); } else { - stepName = this.props.intl.formatMessage( + stepActionName = this.props.intl.formatMessage( { id: `Command.${currentStep.block}` } ); } - let selectedCommandName = ''; - if (this.props.selectedCommandName != null) { - selectedCommandName = this.props.intl.formatMessage( - { id: 'ActionPanel.selectedCommandName' }, - { selectedCommandName: - this.props.intl.formatMessage({id: `Command.${this.props.selectedCommandName}`}) - } - ); - } + const replaceStepAriaLabel = this.props.selectedActionName != null ? + this.props.intl.formatMessage( + {id:'ActionPanel.action.replace.withSelectedAction'}, + { stepNumber, stepActionName, selectedActionName: this.props.intl.formatMessage({id: `Command.${this.props.selectedActionName}`}) } + ) : + this.props.intl.formatMessage({id:'ActionPanel.action.replace.noSelectedAction'}, { stepNumber, stepActionName }); return { - 'stepNumber': stepNumber, - 'stepName': stepName, - 'selectedCommandName': selectedCommandName, - 'previousStepInfo': this.makePreviousStepInfo(), - 'nextStepInfo': this.makeNextStepInfo() - }; + replaceStepAriaLabel, + 'deleteStepAriaLabel': this.props.intl.formatMessage({id:'ActionPanel.action.delete'}, { stepNumber, stepActionName }), + 'previousStepAriaLabel': this.makePreviousStepAriaLabel(stepNumber, stepActionName), + 'nextStepAriaLabel': this.makeNextStepAriaLabel(stepNumber, stepActionName) + } } - makePreviousStepInfo(): ?string { + makePreviousStepAriaLabel(stepNumber: ?number | string, stepActionName: string): string { const currentStep = this.props.programSequence.getProgramStepAt(this.props.pressedStepIndex); if (this.props.pressedStepIndex > 0) { const prevStep = this.props.programSequence.getProgramStepAt(this.props.pressedStepIndex - 1); const cachedPreviousStepLoopData = prevStep.cache; // When previous step is startLoop, aria-label communicates that movePrevious will move out of the current loop if (prevStep.block === 'startLoop' && currentStep.block !== 'endLoop') { - return this.props.intl.formatMessage( - { id: 'CommandInfo.previousStep.startLoop' }, - { loopLabel: prevStep.label } + return this.props.intl.formatMessage({id: 'ActionPanel.action.moveToPreviousStep.outOfLoop'}, + { + stepNumber, + stepActionName, + loopLabel: prevStep.label + } ); // When previous step is endLoop, aria-label communicates that movePrevious will move into a loop } else if (prevStep.block === 'endLoop') { - return this.props.intl.formatMessage( - { id: 'CommandInfo.previousStep.endLoop'}, - { loopLabel: prevStep.label } + return this.props.intl.formatMessage({id: 'ActionPanel.action.moveToPreviousStep.intoLoop'}, + { + stepNumber, + stepActionName, + loopLabel: prevStep.label + } ); } else { // When previous step is not loops and current step is endLoop, calculate the index the loop will move by @@ -97,54 +99,69 @@ class ActionPanel extends React.Component { const startLoopIndex = this.props.programSequence.getMatchingLoopBlockIndex(this.props.pressedStepIndex); const program = this.props.programSequence.getProgram(); if (startLoopIndex != null && program[startLoopIndex - 1] != null) { - return this.props.intl.formatMessage( - { id: 'CommandInfo.previousStep.loop' }, + return this.props.intl.formatMessage({id: 'ActionPanel.action.moveToPreviousStep'}, { + stepNumber, + stepActionName, previousStepNumber: startLoopIndex, - command: this.props.intl.formatMessage({id: `Command.${program[startLoopIndex - 1].block}`}) + previousStepActionName: this.props.intl.formatMessage({id: `Command.${program[startLoopIndex - 1].block}`}), } ) } // When previous step is wrapped in a loop, aria-label communicates position within a loop } else if (cachedPreviousStepLoopData != null) { - return this.props.intl.formatMessage( - { id: 'CommandInfo.previousStep.inLoop'}, + return this.props.intl.formatMessage({id: 'ActionPanel.action.moveToPreviousStep.withinLoop'}, { + stepNumber, + stepActionName, previousStepNumber: cachedPreviousStepLoopData.getContainingLoopPosition(), - command: this.props.intl.formatMessage({id: `Command.${prevStep.block}`}), + previousStepActionName: this.props.intl.formatMessage({id: `Command.${prevStep.block}`}), loopLabel: cachedPreviousStepLoopData.getContainingLoopLabel(), } ) // When previous step is a movements step and not in a loop, aria-label communicates position within the program } else { - return this.props.intl.formatMessage( - { id: 'CommandInfo.previousStep'}, + return this.props.intl.formatMessage({id: 'ActionPanel.action.moveToPreviousStep'}, { + stepNumber, + stepActionName, previousStepNumber: this.props.pressedStepIndex, - command: this.props.intl.formatMessage({id: `Command.${prevStep.block}`}) + previousStepActionName: this.props.intl.formatMessage({id: `Command.${prevStep.block}`}) } ); } } } + return this.props.intl.formatMessage({id: 'ActionPanel.action.moveToPreviousStep.disabled'}, + { + stepNumber, + stepActionName + } + ); } - makeNextStepInfo(): ?string { + makeNextStepAriaLabel(stepNumber: ?number | string, stepActionName: string): string { const currentStep = this.props.programSequence.getProgramStepAt(this.props.pressedStepIndex); if (this.props.pressedStepIndex < (this.props.programSequence.getProgramLength() - 1)) { const nextStep = this.props.programSequence.getProgramStepAt(this.props.pressedStepIndex + 1); const cachedNextStepLoopData = nextStep.cache; // When next step is startLoop, aria-label communicates that moveNext will move into a loop if (nextStep.block === 'startLoop') { - return this.props.intl.formatMessage( - { id: 'CommandInfo.nextStep.startLoop'}, - { loopLabel: nextStep.label } + return this.props.intl.formatMessage({id: 'ActionPanel.action.moveToNextStep.intoLoop'}, + { + stepNumber, + stepActionName, + loopLabel: nextStep.label + } ); // When next step is endLoop, aria-label communicates that moveNext will move out of the current loop } else if (nextStep.block === 'endLoop' && currentStep.block !== 'startLoop') { - return this.props.intl.formatMessage( - { id: 'CommandInfo.nextStep.endLoop'}, - { loopLabel: nextStep.label } + return this.props.intl.formatMessage({id: 'ActionPanel.action.moveToNextStep.outOfLoop'}, + { + stepNumber, + stepActionName, + loopLabel: nextStep.label + } ); } else { // When next step is not loops and current step is startLoop, calculate the index the loop will move by @@ -153,36 +170,45 @@ class ActionPanel extends React.Component { const endLoopIndex = this.props.programSequence.getMatchingLoopBlockIndex(this.props.pressedStepIndex); const program = this.props.programSequence.getProgram(); if (endLoopIndex != null && program[endLoopIndex + 1] != null) { - return this.props.intl.formatMessage( - { id: 'CommandInfo.nextStep.loop' }, + return this.props.intl.formatMessage({id: 'ActionPanel.action.moveToNextStep'}, { + stepNumber, + stepActionName, nextStepNumber: endLoopIndex + 2, - command: this.props.intl.formatMessage({id: `Command.${program[endLoopIndex + 1].block}`}) + nextStepActionName: this.props.intl.formatMessage({id: `Command.${program[endLoopIndex + 1].block}`}), } ); } // When next step is wrapped in a loop, aria-label communicates position within a loop } else if (cachedNextStepLoopData != null) { - return this.props.intl.formatMessage( - { id: 'CommandInfo.nextStep.inLoop'}, + return this.props.intl.formatMessage({id: 'ActionPanel.action.moveToNextStep.withinLoop'}, { + stepNumber, + stepActionName, nextStepNumber: cachedNextStepLoopData.getContainingLoopPosition(), - command: this.props.intl.formatMessage({id: `Command.${nextStep.block}`}), + nextStepActionName: this.props.intl.formatMessage({id: `Command.${nextStep.block}`}), loopLabel: cachedNextStepLoopData.getContainingLoopLabel() } ); // When next step is a movements step and not in a loop, aria-label communicates position within the program } else { - return this.props.intl.formatMessage( - { id: 'CommandInfo.nextStep'}, + return this.props.intl.formatMessage({id: 'ActionPanel.action.moveToNextStep'}, { + stepNumber, + stepActionName, nextStepNumber: this.props.pressedStepIndex + 2, - command: this.props.intl.formatMessage({id: `Command.${nextStep.block}`}) + nextStepActionName: this.props.intl.formatMessage({id: `Command.${nextStep.block}`}) } ); } } } + return this.props.intl.formatMessage({id: 'ActionPanel.action.moveToNextStep.disabled'}, + { + stepNumber, + stepActionName + } + ); } getReplaceIsVisible(): boolean { @@ -208,11 +234,12 @@ class ActionPanel extends React.Component { }; render() { - const stepMessageData = this.makeStepMessageData(); + const ariaLabels = this.makeAriaLabels(); const moveToNextStepIsDisabled = this.props.programSequence.moveToNextStepDisabled(this.props.pressedStepIndex); const moveToPreviousStepIsDisabled = this.props.programSequence.moveToPreviousStepDisabled(this.props.pressedStepIndex); const replaceIsVisible = this.getReplaceIsVisible(); - const replaceIsDisabled = this.props.selectedCommandName == null; + const replaceIsDisabled = this.props.selectedActionName == null; + return (
@@ -226,7 +253,7 @@ class ActionPanel extends React.Component { { name='replaceCurrentStep' disabled={replaceIsDisabled} disabledClassName='ActionPanel__action-buttons--disabled' - aria-label={this.props.intl.formatMessage({id:'ActionPanel.action.replace'}, stepMessageData)} + aria-label={ariaLabels.replaceStepAriaLabel} className='ActionPanel__action-buttons focus-trap-action-panel__action-panel-button' onClick={this.handleClickReplace}> { name='moveToPreviousStep' disabled={moveToPreviousStepIsDisabled} disabledClassName='ActionPanel__action-buttons--disabled' - aria-label={this.props.intl.formatMessage({id:'ActionPanel.action.moveToPreviousStep'}, stepMessageData)} + aria-label={ariaLabels.previousStepAriaLabel} className='ActionPanel__action-buttons focus-trap-action-panel__action-panel-button' onClick={this.handleClickMoveToPreviousStep}> { name='moveToNextStep' disabled={moveToNextStepIsDisabled} disabledClassName='ActionPanel__action-buttons--disabled' - aria-label={this.props.intl.formatMessage({id:'ActionPanel.action.moveToNextStep'}, stepMessageData)} + aria-label={ariaLabels.nextStepAriaLabel} className='ActionPanel__action-buttons focus-trap-action-panel__action-panel-button' onClick={this.handleClickMoveToNextStep}> { test('Given that there is no selected action, then the Replace button should be disabled', () => { const { wrapper } = createMountActionPanel({ pressedStepIndex: 1, - selectedCommandName: null + selectedActionName: null }); const replaceButton = getActionPanelOptionButtons(wrapper, 'replaceCurrentStep'); - const expectedAriaLabel = 'Replace Step 2 turn left 45 degrees '; + const expectedAriaLabel = 'Replace Step 2 turn left 45 degrees'; expect(replaceButton.get(0).props['aria-label']).toBe(expectedAriaLabel); expect(replaceButton.get(0).props['disabled']).toBe(true); }); @@ -127,7 +127,7 @@ describe('ActionPanel options', () => { pressedStepIndex: 0 }); const moveToPreviousStepButton = getActionPanelOptionButtons(wrapper, 'moveToPreviousStep'); - const expectedAriaLabel = 'Move Step 1 forward 1 square '; + const expectedAriaLabel = 'Move Step 1 forward 1 square'; expect(moveToPreviousStepButton.get(0).props['aria-label']).toBe(expectedAriaLabel); expect(moveToPreviousStepButton.get(0).props['disabled']).toBe(true); moveToPreviousStepButton.simulate('click'); @@ -150,7 +150,7 @@ describe('ActionPanel options', () => { pressedStepIndex: 0 }); const moveToPreviousStepButton = getActionPanelOptionButtons(wrapper, 'moveToPreviousStep'); - const expectedAriaLabel = "Move Step 1 loop A "; + const expectedAriaLabel = "Move Step 1 loop A"; expect(moveToPreviousStepButton.get(0).props['disabled']).toBe(true); moveToPreviousStepButton.simulate('click'); expect(moveToPreviousStepButton.get(0).props['aria-label']).toBe(expectedAriaLabel); @@ -173,7 +173,7 @@ describe('ActionPanel options', () => { pressedStepIndex: 2 }); const moveToPreviousStepButton = getActionPanelOptionButtons(wrapper, 'moveToPreviousStep'); - const expectedAriaLabel = "Move Step 3 loop A "; + const expectedAriaLabel = "Move Step 3 loop A"; expect(moveToPreviousStepButton.get(0).props['disabled']).toBe(true); moveToPreviousStepButton.simulate('click'); expect(moveToPreviousStepButton.get(0).props['aria-label']).toBe(expectedAriaLabel); @@ -328,7 +328,7 @@ describe('ActionPanel options', () => { pressedStepIndex: 2 }); const moveToNextStepButton = getActionPanelOptionButtons(wrapper, 'moveToNextStep'); - const expectedAriaLabel = 'Move Step 3 turn right 45 degrees '; + const expectedAriaLabel = 'Move Step 3 turn right 45 degrees'; expect(moveToNextStepButton.get(0).props['aria-label']).toBe(expectedAriaLabel); expect(moveToNextStepButton.get(0).props['disabled']).toBe(true); moveToNextStepButton.simulate('click'); @@ -351,7 +351,7 @@ describe('ActionPanel options', () => { pressedStepIndex: 0 }); const moveToNextStepButton = getActionPanelOptionButtons(wrapper, 'moveToNextStep'); - const expectedAriaLabel = "Move Step 1 loop A "; + const expectedAriaLabel = "Move Step 1 loop A"; expect(moveToNextStepButton.get(0).props['disabled']).toBe(true); moveToNextStepButton.simulate('click'); expect(moveToNextStepButton.get(0).props['aria-label']).toBe(expectedAriaLabel); @@ -374,7 +374,7 @@ describe('ActionPanel options', () => { pressedStepIndex: 2 }); const moveToNextStepButton = getActionPanelOptionButtons(wrapper, 'moveToNextStep'); - const expectedAriaLabel = "Move Step 3 loop A "; + const expectedAriaLabel = "Move Step 3 loop A"; expect(moveToNextStepButton.get(0).props['disabled']).toBe(true); moveToNextStepButton.simulate('click'); expect(moveToNextStepButton.get(0).props['aria-label']).toBe(expectedAriaLabel); diff --git a/src/ActionsMenuItem.js b/src/ActionsMenuItem.js index 79e95338..2e248ce8 100644 --- a/src/ActionsMenuItem.js +++ b/src/ActionsMenuItem.js @@ -34,8 +34,8 @@ export class ActionsMenuItem extends React.Component< ActionsMenuItemProps, {} > render () { // We don't use FormattedMessage as we are working with a complex chain of templates. - let commandName = this.props.intl.formatMessage({ id: `ActionsMenuItem.command.${this.props.itemKey}` }); - const usedLabel = this.props.intl.formatMessage({ id: 'ActionsMenuItem.usedItemToggleLabel' }); + let commandName = this.props.intl.formatMessage({ id: `UI.ActionsMenuItem.command.${this.props.itemKey}` }); + const usedLabel = this.props.intl.formatMessage({ id: 'UI.ActionsMenuItem.usedItemToggleLabel' }); if (this.props.isUsed) { commandName += " " + usedLabel; } diff --git a/src/ActionsSimplificationModal.js b/src/ActionsSimplificationModal.js index c8f57552..f7a397a3 100644 --- a/src/ActionsSimplificationModal.js +++ b/src/ActionsSimplificationModal.js @@ -54,14 +54,14 @@ class ActionsSimplificationModal extends React.Component

- +

@@ -91,7 +91,7 @@ class ActionsSimplificationModal extends React.Component - +
diff --git a/src/AnnouncementBuilder.js b/src/AnnouncementBuilder.js index dbfb0be6..bd88dfed 100644 --- a/src/AnnouncementBuilder.js +++ b/src/AnnouncementBuilder.js @@ -16,21 +16,21 @@ export default class AnnouncementBuilder { } buildSelectActionAnnouncement(action: string): AnnouncementData { - let commandType = null; + let actionType = null; if (action === 'loop') { - commandType = this.intl.formatMessage({ + actionType = this.intl.formatMessage({ id: 'Announcement.control' }); } else { - commandType = this.intl.formatMessage({ + actionType = this.intl.formatMessage({ id: 'Announcement.movement' }); } return { messageIdSuffix: 'actionSelected', values: { - commandType: commandType, - command: this.intl.formatMessage({ + actionType, + actionName: this.intl.formatMessage({ id: `Announcement.${action}` }), } @@ -38,21 +38,21 @@ export default class AnnouncementBuilder { } buildAddStepAnnouncement(action: string): AnnouncementData { - let commandType = null; + let actionType = null; if (action === 'loop') { - commandType = this.intl.formatMessage({ + actionType = this.intl.formatMessage({ id: 'Announcement.control' }); } else { - commandType = this.intl.formatMessage({ + actionType = this.intl.formatMessage({ id: 'Announcement.movement' }); } return { messageIdSuffix: 'add', values: { - commandType: commandType, - command: this.intl.formatMessage({ + actionType, + actionName: this.intl.formatMessage({ id: `Announcement.${action}` }), } @@ -64,10 +64,10 @@ export default class AnnouncementBuilder { return { messageIdSuffix: 'delete', values: { - commandType: this.intl.formatMessage({ + actionType: this.intl.formatMessage({ id: "Announcement.control" }), - command: this.intl.formatMessage( + actionName: this.intl.formatMessage( { id: `Announcement.${programBlock.block}` }, @@ -81,10 +81,10 @@ export default class AnnouncementBuilder { return { messageIdSuffix: 'delete', values: { - commandType: this.intl.formatMessage({ + actionType: this.intl.formatMessage({ id: "Announcement.movement" }), - command: this.intl.formatMessage( + actionName: this.intl.formatMessage( { id: `Announcement.${programBlock.block}` } @@ -100,10 +100,10 @@ export default class AnnouncementBuilder { return { messageIdSuffix: 'replace', values: { - oldCommand: this.intl.formatMessage({ + oldActionName: this.intl.formatMessage({ id: `Announcement.${programBlock.block}` }), - newCommand: this.intl.formatMessage({ + newActionName: this.intl.formatMessage({ id: `Announcement.${selectedAction}` }) } diff --git a/src/AnnouncementBuilder.test.js b/src/AnnouncementBuilder.test.js index b9858765..0c986ed5 100644 --- a/src/AnnouncementBuilder.test.js +++ b/src/AnnouncementBuilder.test.js @@ -22,16 +22,16 @@ test('Test buildSelectActionAnnouncement()', () => { expect(announcementBuilder.buildSelectActionAnnouncement('loop')).toStrictEqual({ messageIdSuffix: 'actionSelected', values: { - commandType: 'control', - command: 'loop' + actionType: 'control', + actionName: 'loop' } }); expect(announcementBuilder.buildSelectActionAnnouncement('forward1')).toStrictEqual({ messageIdSuffix: 'actionSelected', values: { - commandType: 'movement', - command: 'forward 1 square' + actionType: 'movement', + actionName: 'forward 1 square' } }); }); @@ -44,16 +44,16 @@ test('Test buildAddStepAnnouncement()', () => { expect(announcementBuilder.buildAddStepAnnouncement('loop')).toStrictEqual({ messageIdSuffix: 'add', values: { - commandType: 'control', - command: 'loop' + actionType: 'control', + actionName: 'loop' } }); expect(announcementBuilder.buildAddStepAnnouncement('forward1')).toStrictEqual({ messageIdSuffix: 'add', values: { - commandType: 'movement', - command: 'forward 1 square' + actionType: 'movement', + actionName: 'forward 1 square' } }); }); @@ -71,8 +71,8 @@ describe('Test buildDeleteStepAnnouncement()', () => { expect(announcementBuilder.buildDeleteStepAnnouncement(startLoopBlock)).toStrictEqual({ messageIdSuffix: 'delete', values: { - commandType: 'control', - command: 'loop A' + actionType: 'control', + actionName: 'loop A' } }); }); @@ -88,8 +88,8 @@ describe('Test buildDeleteStepAnnouncement()', () => { expect(announcementBuilder.buildDeleteStepAnnouncement(endLoopBlock)).toStrictEqual({ messageIdSuffix: 'delete', values: { - commandType: 'control', - command: 'loop A' + actionType: 'control', + actionName: 'loop A' } }); }); @@ -104,8 +104,8 @@ describe('Test buildDeleteStepAnnouncement()', () => { expect(announcementBuilder.buildDeleteStepAnnouncement(forwardBlock)).toStrictEqual({ messageIdSuffix: 'delete', values: { - commandType: 'movement', - command: 'forward 1 square' + actionType: 'movement', + actionName: 'forward 1 square' } }); }); @@ -123,8 +123,8 @@ test('Test buildReplaceStepAnnouncement()', () => { expect(announcementBuilder.buildReplaceStepAnnouncement(forwardBlock, 'right45')).toStrictEqual({ messageIdSuffix: 'replace', values: { - oldCommand: 'forward 1 square', - newCommand: 'turn right 45 degrees' + oldActionName: 'forward 1 square', + newActionName: 'turn right 45 degrees' } }); }); diff --git a/src/App.js b/src/App.js index 412c9308..49683450 100644 --- a/src/App.js +++ b/src/App.js @@ -266,7 +266,7 @@ export class App extends React.Component { }); } - getSelectedCommandName() { + getSelectedActionName() { if (this.state.selectedAction !== null) { return this.state.selectedAction; } else { @@ -960,7 +960,7 @@ export class App extends React.Component { { className="App__PrivacyModal__toggle-button" onClick={this.handleClickPrivacyButton} > - +
{

- +

- +

@@ -1455,7 +1455,7 @@ export class App extends React.Component {

- +

@@ -1548,7 +1548,7 @@ export class App extends React.Component { aria-hidden={true} />
- {this.props.intl.formatMessage({id:'ShareButton'})} + {this.props.intl.formatMessage({id:'UI.ShareButton'})}
diff --git a/src/App.test.js b/src/App.test.js index 697600e6..e3e4ed3b 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -56,7 +56,7 @@ it('renders without crashing', () => { ReactDOM.unmountComponentAtNode(div); }); -it('Should play a sound when selectedCommandName changes', () => { +it('Should play a sound when selectedActionName changes', () => { const { app, audioManagerMock } = mountApp({}); // Update the selectedAction @@ -64,8 +64,8 @@ it('Should play a sound when selectedCommandName changes', () => { expect(audioManagerMock.playAnnouncement.mock.calls.length).toBe(1); expect(audioManagerMock.playAnnouncement.mock.calls[0][0]).toBe('actionSelected'); expect(audioManagerMock.playAnnouncement.mock.calls[0][2]).toStrictEqual({ - "commandType": "movement", - "command": "forward 1 square" + "actionType": "movement", + "actionName": "forward 1 square" }); }); }); diff --git a/src/CharacterAriaLive.js b/src/CharacterAriaLive.js index 6365cbcc..a37bd9f8 100644 --- a/src/CharacterAriaLive.js +++ b/src/CharacterAriaLive.js @@ -40,7 +40,7 @@ class CharacterAriaLive extends React.Component { }); const text = this.props.intl.formatMessage( { id:'CharacterAriaLive.movementAriaLabel' }, - { character: characterLabel } + { sceneCharacter: characterLabel } ); this.setLiveRegion(text); } diff --git a/src/CharacterDescriptionBuilder.js b/src/CharacterDescriptionBuilder.js index 481b8136..8e3a4ad3 100644 --- a/src/CharacterDescriptionBuilder.js +++ b/src/CharacterDescriptionBuilder.js @@ -28,7 +28,7 @@ export default class CharacterDescriptionBuilder { this.intl ); - const directionLabel = this.intl.formatMessage({id: `Direction.${characterState.direction}`}); + const facingDirectionLabel = this.intl.formatMessage({id: `FacingDirection.${characterState.direction}`}); if (itemLabel) { return this.intl.formatMessage( @@ -38,8 +38,8 @@ export default class CharacterDescriptionBuilder { { columnLabel: columnLabel, rowLabel: rowLabel, - item: itemLabel, - direction: directionLabel + backgroundItem: itemLabel, + facingDirection: facingDirectionLabel } ); } else { @@ -50,7 +50,7 @@ export default class CharacterDescriptionBuilder { { columnLabel: columnLabel, rowLabel: rowLabel, - direction: directionLabel + facingDirection: facingDirectionLabel } ); } diff --git a/src/CharacterMessageBuilder.js b/src/CharacterMessageBuilder.js index c04db41a..43d6a580 100644 --- a/src/CharacterMessageBuilder.js +++ b/src/CharacterMessageBuilder.js @@ -27,7 +27,7 @@ export default class CharacterMessageBuilder { buildEndOfSceneMessage(): string { return this.intl.formatMessage( { - id:'CharacterMessageBuilder.endOfScene' + id:'UI.CharacterMessageBuilder.endOfScene' } ); } @@ -37,7 +37,7 @@ export default class CharacterMessageBuilder { const rowLabel = this.dimensions.getRowLabel(y); return this.intl.formatMessage( { - id:'CharacterMessageBuilder.hitWall' + id:'UI.CharacterMessageBuilder.hitWall' }, { columnLabel: columnLabel == null ? '' : columnLabel, diff --git a/src/CommandPaletteCommand.js b/src/CommandPaletteCommand.js index a6f083d1..90c3fd69 100644 --- a/src/CommandPaletteCommand.js +++ b/src/CommandPaletteCommand.js @@ -12,7 +12,7 @@ type CommandPaletteCommandProps = { commandName: CommandName, intl: IntlShape, isDraggingCommand: boolean, - selectedCommandName: ?CommandName, + selectedActionName: ?CommandName, onSelect: (commandName: CommandName) => void, onDragStart: (commandName: CommandName) => void, onDragEnd: () => void @@ -34,7 +34,7 @@ class CommandPaletteCommand extends React.Component { {}}/> ); expect(hasPressedClass(wrapper)).toBe(false); @@ -43,7 +43,7 @@ test('Pressed state is false when selecedCommandName is another command', () => {}}/> ); expect(hasPressedClass(wrapper)).toBe(false); @@ -55,7 +55,7 @@ test('Pressed state is true when selecedCommandName is this command', () => { {}}/> ); expect(hasPressedClass(wrapper)).toBe(true); @@ -69,7 +69,7 @@ test('Clicking the button calls the callback onSelect with commandName', () => { ); @@ -80,8 +80,8 @@ test('Clicking the button calls the callback onSelect with commandName', () => { // Verify that onSelect is called with the commandName expect(mockSelectHandler.mock.calls.length).toBe(1); expect(mockSelectHandler.mock.calls[0][0]).toBe('forward1'); - // Update the selectedCommandName - wrapper.setProps({selectedCommandName: 'forward1'}); + // Update the selectedActionName + wrapper.setProps({selectedActionName: 'forward1'}); wrapper.update(); // Click again button.simulate('click'); diff --git a/src/ConfirmDeleteAllModal.js b/src/ConfirmDeleteAllModal.js index be203dc9..d99f67ac 100644 --- a/src/ConfirmDeleteAllModal.js +++ b/src/ConfirmDeleteAllModal.js @@ -23,24 +23,24 @@ class ConfirmDeleteAllModal extends React.Component
- +
diff --git a/src/DesignModeCursorDescriptionBuilder.js b/src/DesignModeCursorDescriptionBuilder.js index 00ce7fec..05bb6ab4 100644 --- a/src/DesignModeCursorDescriptionBuilder.js +++ b/src/DesignModeCursorDescriptionBuilder.js @@ -36,7 +36,7 @@ export default class DesignModeCursorDescriptionBuilder { { columnLabel: columnLabel, rowLabel: rowLabel, - item: itemLabel + backgroundItem: itemLabel } ); } else { diff --git a/src/KeyboardInputModal.js b/src/KeyboardInputModal.js index f8dbb1be..dc4fde65 100644 --- a/src/KeyboardInputModal.js +++ b/src/KeyboardInputModal.js @@ -115,12 +115,12 @@ class KeyboardInputModal extends React.Component {altKeyIcon} @@ -129,12 +129,12 @@ class KeyboardInputModal extends React.Component {controlKeyIcon} @@ -142,7 +142,7 @@ class KeyboardInputModal extends React.Component
@@ -152,7 +152,7 @@ class KeyboardInputModal extends React.Component
@@ -165,7 +165,7 @@ class KeyboardInputModal extends React.Component { - const messageId = "KeyboardInputModal.Scheme.Descriptions." + schemeName; + const messageId = "UI.KeyboardInputModal.Scheme.Descriptions." + schemeName; const optionText = this.props.intl.formatMessage({ id: messageId }); selectOptionElements.push(
diff --git a/src/KeyboardInputSchemes.js b/src/KeyboardInputSchemes.js index ff9e377f..2f382987 100644 --- a/src/KeyboardInputSchemes.js +++ b/src/KeyboardInputSchemes.js @@ -543,20 +543,20 @@ export const KeyboardInputSchemes: KeyboardInputSchemesType = { }; const labelMessageKeysByCode = { - "KeyA": "KeyboardInputModal.KeyLabels.A", - "KeyB": "KeyboardInputModal.KeyLabels.B", - "KeyD": "KeyboardInputModal.KeyLabels.D", - "KeyE": "KeyboardInputModal.KeyLabels.E", - "KeyI": "KeyboardInputModal.KeyLabels.I", - "KeyP": "KeyboardInputModal.KeyLabels.P", - "KeyS": "KeyboardInputModal.KeyLabels.S", - "KeyR": "KeyboardInputModal.KeyLabels.R" + "KeyA": "UI.KeyboardInputModal.KeyLabels.A", + "KeyB": "UI.KeyboardInputModal.KeyLabels.B", + "KeyD": "UI.KeyboardInputModal.KeyLabels.D", + "KeyE": "UI.KeyboardInputModal.KeyLabels.E", + "KeyI": "UI.KeyboardInputModal.KeyLabels.I", + "KeyP": "UI.KeyboardInputModal.KeyLabels.P", + "KeyS": "UI.KeyboardInputModal.KeyLabels.S", + "KeyR": "UI.KeyboardInputModal.KeyLabels.R" }; const labelMessageKeysByKey = { - "?": "KeyboardInputModal.KeyLabels.QuestionMark", - ">": "KeyboardInputModal.KeyLabels.GreaterThan", - "<": "KeyboardInputModal.KeyLabels.LessThan" + "?": "UI.KeyboardInputModal.KeyLabels.QuestionMark", + ">": "UI.KeyboardInputModal.KeyLabels.GreaterThan", + "<": "UI.KeyboardInputModal.KeyLabels.LessThan" }; export function getLabelMessageKeyFromKeyDef (keyDef: KeyDef) { @@ -572,20 +572,20 @@ export function getLabelMessageKeyFromKeyDef (keyDef: KeyDef) { }; const iconMessageKeysByCode = { - "KeyA": "KeyboardInputModal.KeyIcons.A", - "KeyB": "KeyboardInputModal.KeyIcons.B", - "KeyD": "KeyboardInputModal.KeyIcons.D", - "KeyE": "KeyboardInputModal.KeyIcons.E", - "KeyI": "KeyboardInputModal.KeyIcons.I", - "KeyP": "KeyboardInputModal.KeyIcons.P", - "KeyS": "KeyboardInputModal.KeyIcons.S", - "KeyR": "KeyboardInputModal.KeyIcons.R" + "KeyA": "UI.KeyboardInputModal.KeyIcons.A", + "KeyB": "UI.KeyboardInputModal.KeyIcons.B", + "KeyD": "UI.KeyboardInputModal.KeyIcons.D", + "KeyE": "UI.KeyboardInputModal.KeyIcons.E", + "KeyI": "UI.KeyboardInputModal.KeyIcons.I", + "KeyP": "UI.KeyboardInputModal.KeyIcons.P", + "KeyS": "UI.KeyboardInputModal.KeyIcons.S", + "KeyR": "UI.KeyboardInputModal.KeyIcons.R" }; const iconMessageKeysByKey = { - "?": "KeyboardInputModal.KeyIcons.QuestionMark", - ">": "KeyboardInputModal.KeyIcons.GreaterThan", - "<": "KeyboardInputModal.KeyIcons.LessThan" + "?": "UI.KeyboardInputModal.KeyIcons.QuestionMark", + ">": "UI.KeyboardInputModal.KeyIcons.GreaterThan", + "<": "UI.KeyboardInputModal.KeyIcons.LessThan" }; export function getIconMessageKeyFromKeyDef (keyDef: KeyDef) { diff --git a/src/PrivacyModal.js b/src/PrivacyModal.js index 0bd5edca..ce812a77 100644 --- a/src/PrivacyModal.js +++ b/src/PrivacyModal.js @@ -20,7 +20,7 @@ type PrivacyModalProps = { class PrivacyModal extends React.Component { render() { const closeButtonProperties = { - label: this.props.intl.formatMessage({ id: 'PrivacyModal.close'} ), + label: this.props.intl.formatMessage({ id: 'UI.Close'} ), isPrimary: true, onClick: this.props.onClose }; @@ -35,114 +35,78 @@ class PrivacyModal extends React.Component { >
-
Updated January 4th, 2024
+
{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section010.Heading'})}
-

- At Weavly, we believe that privacy is a fundamental human right, and acknowledge how important - it is to our community—especially with regards to both children and parents. -

-

This page explains:

+

{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section010.block010'})}

+

{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section010.block020'})}

    -
  • - What type of information we store on our website (http://create.weavly.org/) -
  • -
  • - How that information is used and processed -
  • -
  • - How we keep your information safe -
  • +
  • {this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section010.block030.item010'})}
  • +
  • {this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section010.block030.item020'})}
  • +
  • {this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section010.block030.item030'})}
-
What information does Weavly store?
+
{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section020.Heading'})}
-

- As you use Weavly, we may store information on how Weavly is accessed and used by you. We store - the following information independently on every browser/device pair that you use Weavly on: -

+

{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section020.block010'})}

    -
  • Your prefered settings for display colour theme and keyboard shortcuts
  • -
  • Your visible set of action blocks on the action panel
  • -
  • Your selected background for the scene
  • -
  • Your created program
  • -
  • Any line that is drawn on the scene as a result of running your program
  • -
  • The last position of your character on the scene
  • -
  • The starting position of your character
  • -
  • The version of Weavly that was used
  • +
  • {this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section020.block020.item010'})}
  • +
  • {this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section020.block020.item020'})}
  • +
  • {this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section020.block020.item030'})}
  • +
  • {this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section020.block020.item040'})}
  • +
  • {this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section020.block020.item050'})}
  • +
  • {this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section020.block020.item060'})}
  • +
  • {this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section020.block020.item070'})}
  • +
  • {this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section020.block020.item080'})}
-

- You can delete any information that Weavly has generated from your usage by clearing your - browser's cache and local storage. Check your browser's documentation for details. -

+

{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section020.block030'})}

-
How does Weavly use this information?
+
{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section030.Heading'})}
-

- The generated information is kept in a local storage on your device to make your use of Weavly - more convenient: -

+

{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section030.block010'})}

    -
  • - Your settings for the coding environment are stored so you don't have to adjust them every - time you launch Weavly -
  • -
  • - If you happen to accidentally or intentionally close your browser, the coding environment - will be the same as when you left it for the next time you launch Weavly -
  • +
  • {this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section030.block020.item010'})}
  • +
  • {this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section030.block020.item020'})}
-

- Although storing your information in the browser makes it convenient - for every time you access Weavly, it may cause problems on shared computers. As a result, - someone that uses the computer after you may be able to access your Weavly settings and program. -

+

{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section030.block030'})}

-
How do we keep your information safe?
+
{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section040.Heading'})}
-

The security of your data is important to us, but please keep in mind that no method of - electronic storage is 100% secure. We currently use browser local storage to store the specified - data. The information is stored in the browser by domain (create.weavly.org) and only code - running from that domain may access it. Thus, other websites cannot access the data. However, - local storage is not encrypted on disk and someone with access to the device could get access to - the data.

+

{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section040.block010'})}

-
Children's Privacy
+
{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section050.Heading'})}
-

- We do not knowingly collect personally identifiable information from anyone under the age of 18. - If you are a parent or guardian and you have become aware that we have collected Personal Data - from your children without verification of parental consent, we can take steps to remove that - information from our servers. Please contact us. +

${this.props.intl.formatMessage({ id: 'UI.PrivacyModal.contactUs' })}` } + )} + }>

-
Changes to This Privacy Policy
+
{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section060.Heading'})}
-

- We may update our Privacy Policy from time to time. We will notify you of any changes by posting - the new Privacy Policy on this page. We will let you know of any changes becoming effective by - updating the “effective date” at the top of this Privacy Policy. You are advised to review this - Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when - they are posted on this page. -

+

{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section060.block010'})}

-
Contact Us
+
{this.props.intl.formatMessage({ id: 'UI.PrivacyModal.section070.Heading'})}
-

- If you have any questions about this Privacy Policy, - please contact - us. +

${this.props.intl.formatMessage({ id: 'UI.PrivacyModal.contactUs' })}` } + )} + }>

diff --git a/src/ProgramBlockEditor.js b/src/ProgramBlockEditor.js index 156a4c17..7310f393 100644 --- a/src/ProgramBlockEditor.js +++ b/src/ProgramBlockEditor.js @@ -455,7 +455,7 @@ export class ProgramBlockEditor extends React.Component

- +

{ expect(audioManagerMock.playAnnouncement.mock.calls.length).toBe(1); expect(audioManagerMock.playAnnouncement.mock.calls[0][0]).toBe('add'); expect(audioManagerMock.playAnnouncement.mock.calls[0][2]).toStrictEqual({ - commandType: 'movement', - command: 'forward 3 squares' + actionType: 'movement', + actionName: 'forward 3 squares' }); // The focus, scrolling, and animation should be set up @@ -135,8 +135,8 @@ describe('Test addSelectedActionToProgramEnd()', () => { expect(audioManagerMock.playAnnouncement.mock.calls.length).toBe(1); expect(audioManagerMock.playAnnouncement.mock.calls[0][0]).toBe('add'); expect(audioManagerMock.playAnnouncement.mock.calls[0][2]).toStrictEqual({ - commandType: 'movement', - command: 'forward 3 squares' + actionType: 'movement', + actionName: 'forward 3 squares' }); // The focus, scrolling, and animation should be set up @@ -195,8 +195,8 @@ type DeleteStepTestCase = { program: Program, deleteStepIndex: number, deleteStepName: string, - expectedAnnouncementCommandType: string, - expectedAnnouncementCommand: string + expectedAnnouncementActionType: string, + expectedAnnouncementActionName: string }; describe('Test deleteProgramStep()', () => { @@ -208,8 +208,8 @@ describe('Test deleteProgramStep()', () => { ], deleteStepIndex: 0, deleteStepName: 'forward1', - expectedAnnouncementCommandType: 'movement', - expectedAnnouncementCommand: 'forward 1 square' + expectedAnnouncementActionType: 'movement', + expectedAnnouncementActionName: 'forward 1 square' }, { program: [ @@ -219,8 +219,8 @@ describe('Test deleteProgramStep()', () => { ], deleteStepIndex: 0, deleteStepName: 'startLoop', - expectedAnnouncementCommandType: 'control', - expectedAnnouncementCommand: 'loop A' + expectedAnnouncementActionType: 'control', + expectedAnnouncementActionName: 'loop A' }, { program: [ @@ -230,8 +230,8 @@ describe('Test deleteProgramStep()', () => { ], deleteStepIndex: 1, deleteStepName: 'endLoop', - expectedAnnouncementCommandType: 'control', - expectedAnnouncementCommand: 'loop A' + expectedAnnouncementActionType: 'control', + expectedAnnouncementActionName: 'loop A' } ]: Array))('When deleting a step not at the end, then focus is set to the step now at the deleted index', (testData: DeleteStepTestCase, done) => { @@ -252,8 +252,8 @@ describe('Test deleteProgramStep()', () => { expect(audioManagerMock.playAnnouncement.mock.calls.length).toBe(1); expect(audioManagerMock.playAnnouncement.mock.calls[0][0]).toBe('delete'); expect(audioManagerMock.playAnnouncement.mock.calls[0][2]).toStrictEqual({ - commandType: testData.expectedAnnouncementCommandType, - command: testData.expectedAnnouncementCommand + actionType: testData.expectedAnnouncementActionType, + actionName: testData.expectedAnnouncementActionName }); // The add-node after the program should be focused @@ -280,8 +280,8 @@ describe('Test deleteProgramStep()', () => { ], deleteStepIndex: 1, deleteStepName: 'forward2', - expectedAnnouncementCommandType: 'movement', - expectedAnnouncementCommand: 'forward 2 squares' + expectedAnnouncementActionType: 'movement', + expectedAnnouncementActionName: 'forward 2 squares' }, { program: [ @@ -291,8 +291,8 @@ describe('Test deleteProgramStep()', () => { ], deleteStepIndex: 1, deleteStepName: 'startLoop', - expectedAnnouncementCommandType: 'control', - expectedAnnouncementCommand: 'loop A' + expectedAnnouncementActionType: 'control', + expectedAnnouncementActionName: 'loop A' }, { program: [ @@ -302,8 +302,8 @@ describe('Test deleteProgramStep()', () => { ], deleteStepIndex: 2, deleteStepName: 'endLoop', - expectedAnnouncementCommandType: 'control', - expectedAnnouncementCommand: 'loop A' + expectedAnnouncementActionType: 'control', + expectedAnnouncementActionName: 'loop A' } ]: Array))('When deleting the step at the end, then focus is set to the add-node after the program', (testData: DeleteStepTestCase, done) => { @@ -324,8 +324,8 @@ describe('Test deleteProgramStep()', () => { expect(audioManagerMock.playAnnouncement.mock.calls.length).toBe(1); expect(audioManagerMock.playAnnouncement.mock.calls[0][0]).toBe('delete'); expect(audioManagerMock.playAnnouncement.mock.calls[0][2]).toStrictEqual({ - commandType: testData.expectedAnnouncementCommandType, - command: testData.expectedAnnouncementCommand + actionType: testData.expectedAnnouncementActionType, + actionName: testData.expectedAnnouncementActionName }); // The add-node after the program should be focused @@ -463,8 +463,8 @@ describe('Test replaceProgramStep()', () => { expect(audioManagerMock.playAnnouncement.mock.calls.length).toBe(1); expect(audioManagerMock.playAnnouncement.mock.calls[0][0]).toBe('replace'); expect(audioManagerMock.playAnnouncement.mock.calls[0][2]).toStrictEqual({ - oldCommand: "turn left 45 degrees", - newCommand: "turn right 45 degrees" + oldActionName: "turn left 45 degrees", + newActionName: "turn right 45 degrees" }); // The focus, scrolling, and animation should be set up @@ -501,7 +501,7 @@ describe('Test moveProgramStepNext()', () => { {block: 'forward2'}, {block: 'forward1'} ], - expectedAnnouncementCommand: 'moveToNext', + expectedAnnouncementAction: 'moveToNext', expectedFocusCommandBlockAfterUpdateCall: 1 }, { @@ -520,7 +520,7 @@ describe('Test moveProgramStepNext()', () => { {block: 'startLoop', label: 'A', iterations: 1}, {block: 'endLoop', label: 'A'}, ], - expectedAnnouncementCommand: 'moveToNext', + expectedAnnouncementAction: 'moveToNext', expectedFocusCommandBlockAfterUpdateCall: 0 } ]))('When movement is possible, then the program should be updated and all expected activities invoked', (testData, done) => { @@ -535,7 +535,7 @@ describe('Test moveProgramStepNext()', () => { // The announcement should be made expect(audioManagerMock.playAnnouncement.mock.calls.length).toBe(1); - expect(audioManagerMock.playAnnouncement.mock.calls[0][0]).toBe(testData.expectedAnnouncementCommand); + expect(audioManagerMock.playAnnouncement.mock.calls[0][0]).toBe(testData.expectedAnnouncementAction); // $FlowFixMe: Jest mock API const programBlockEditorMock = ProgramBlockEditor.mock.instances[0]; @@ -593,7 +593,7 @@ describe('Test moveProgramStepPrevious()', () => { {block: 'forward2'}, {block: 'forward1'} ], - expectedAnnouncementCommand: 'moveToPrevious', + expectedAnnouncementAction: 'moveToPrevious', expectedFocusCommandBlockAfterUpdateCall: 1 }, { @@ -612,7 +612,7 @@ describe('Test moveProgramStepPrevious()', () => { {block: 'forward1'}, {block: 'forward2'} ], - expectedAnnouncementCommand: 'moveToPrevious', + expectedAnnouncementAction: 'moveToPrevious', expectedFocusCommandBlockAfterUpdateCall: 0 }, ]))('When movement is possible, then the program should be updated and all expected activities invoked', (testData, done) => { @@ -627,7 +627,7 @@ describe('Test moveProgramStepPrevious()', () => { // The announcement should be made expect(audioManagerMock.playAnnouncement.mock.calls.length).toBe(1); - expect(audioManagerMock.playAnnouncement.mock.calls[0][0]).toBe(testData.expectedAnnouncementCommand); + expect(audioManagerMock.playAnnouncement.mock.calls[0][0]).toBe(testData.expectedAnnouncementAction); // $FlowFixMe: Jest mock API const programBlockEditorMock = ProgramBlockEditor.mock.instances[0]; diff --git a/src/Scene.js b/src/Scene.js index 251ad553..b9be352e 100644 --- a/src/Scene.js +++ b/src/Scene.js @@ -68,7 +68,7 @@ class Scene extends React.Component { } generateAriaLabel() { - const worldLabel = this.props.intl.formatMessage({id: this.props.world + '.name'}); + const backgroundName = this.props.intl.formatMessage({id: `UI.${this.props.world}.name`}); const numColumns = this.props.dimensions.getWidth(); const numRows = this.props.dimensions.getHeight(); @@ -81,10 +81,10 @@ class Scene extends React.Component { return this.props.intl.formatMessage( { id: 'Scene.description' }, { - world: worldLabel, - numColumns: numColumns, - numRows: numRows, - characterDescription: characterDescription + backgroundName, + numColumns, + numRows, + characterDescription } ); } diff --git a/src/ShareModal.js b/src/ShareModal.js index 4be33db7..e639dac5 100644 --- a/src/ShareModal.js +++ b/src/ShareModal.js @@ -27,34 +27,34 @@ class ShareModal extends React.Component { render () { const buttonProperties = [{ - label: this.props.intl.formatMessage({id: 'ShareModal.close'}), + label: this.props.intl.formatMessage({id: 'UI.Close'}), onClick: this.props.onClose, isPrimary: false }]; - const copyButtonLabel = this.props.intl.formatMessage({ id: 'ShareModal.copy'}); + const copyButtonLabel = this.props.intl.formatMessage({ id: 'UI.ShareModal.copy'}); return(
-

-

+

+

- {this.props.intl.formatMessage({ id: 'SoundOptionsModal.toggleOff' })} + {this.props.intl.formatMessage({ id: 'UI.Off' })}
- {this.props.intl.formatMessage({ id: 'SoundOptionsModal.toggleOn' })} + {this.props.intl.formatMessage({ id: 'UI.On' })}
@@ -126,14 +126,14 @@ class SoundOptionsModal extends React.Component diff --git a/src/ThemeSelector.js b/src/ThemeSelector.js index a241417d..a85dcd8b 100644 --- a/src/ThemeSelector.js +++ b/src/ThemeSelector.js @@ -106,7 +106,7 @@ class ThemeSelector extends React.Component - +
); } @@ -131,13 +131,13 @@ class ThemeSelector extends React.Component diff --git a/src/WorldSelector.js b/src/WorldSelector.js index a461bda2..630cc5b4 100644 --- a/src/WorldSelector.js +++ b/src/WorldSelector.js @@ -127,7 +127,7 @@ class WorldSelector extends React.Component
@@ -166,20 +166,20 @@ class WorldSelector extends React.Component
- +
{this.renderWorldOptions()}
diff --git a/src/messages.json b/src/messages.json index 9ebdb2df..ef764586 100644 --- a/src/messages.json +++ b/src/messages.json @@ -1,24 +1,19 @@ { "en": { - "ActionPanel.action.delete": "Delete Step {stepNumber} {stepName}", - "ActionPanel.action.moveToNextStep": "Move Step {stepNumber} {stepName} {nextStepInfo}", - "ActionPanel.action.moveToPreviousStep": "Move Step {stepNumber} {stepName} {previousStepInfo}", - "ActionPanel.action.replace": "Replace Step {stepNumber} {stepName} {selectedCommandName}", - "ActionPanel.selectedCommandName": "with selected action {selectedCommandName}", - "ActionsMenu.title": "Actions", + "ActionPanel.action.delete": "Delete Step {stepNumber} {stepActionName}", + "ActionPanel.action.moveToNextStep": "Move Step {stepNumber} {stepActionName} after step {nextStepNumber} {nextStepActionName}", + "ActionPanel.action.moveToNextStep.disabled": "Move Step {stepNumber} {stepActionName}", + "ActionPanel.action.moveToNextStep.intoLoop": "Move Step {stepNumber} {stepActionName} into loop {loopLabel}", + "ActionPanel.action.moveToNextStep.outOfLoop": "Move Step {stepNumber} {stepActionName} out of loop {loopLabel}", + "ActionPanel.action.moveToNextStep.withinLoop": "Move Step {stepNumber} {stepActionName} after step {nextStepNumber} {nextStepActionName} of loop {loopLabel}", + "ActionPanel.action.moveToPreviousStep": "Move Step {stepNumber} {stepActionName} before step {previousStepNumber} {previousStepActionName}", + "ActionPanel.action.moveToPreviousStep.disabled": "Move Step {stepNumber} {stepActionName}", + "ActionPanel.action.moveToPreviousStep.intoLoop": "Move Step {stepNumber} {stepActionName} into loop {loopLabel}", + "ActionPanel.action.moveToPreviousStep.outOfLoop": "Move Step {stepNumber} {stepActionName} out of loop {loopLabel}", + "ActionPanel.action.moveToPreviousStep.withinLoop": "Move Step {stepNumber} {stepActionName} before step {previousStepNumber} {previousStepActionName} of loop {loopLabel}", + "ActionPanel.action.replace.noSelectedAction": "Replace Step {stepNumber} {stepActionName}", + "ActionPanel.action.replace.withSelectedAction": "Replace Step {stepNumber} {stepActionName} with selected action {selectedActionName}", "ActionsMenu.toggleActionsMenu": "configure available actions", - "ActionsMenuItem.command.backward1": "Move backward 1 step", - "ActionsMenuItem.command.forward1": "Move forward 1 step", - "ActionsMenuItem.command.left45": "Turn left 45 degrees", - "ActionsMenuItem.command.left90": "Turn left 90 degrees", - "ActionsMenuItem.command.loop": "Loop", - "ActionsMenuItem.command.right45": "Turn right 45 degrees", - "ActionsMenuItem.command.right90": "Turn right 90 degrees", - "ActionsMenuItem.unusedItemToggleLabel": "{action}", - "ActionsMenuItem.usedItemToggleLabel": "(Used)", - "ActionsSimplificationModal.cancel": "Cancel", - "ActionsSimplificationModal.save": "Save", - "ActionsSimplificationModal.title": "Available Actions", "AmusementPark.character": "the train", "AmusementPark.entrance": "entrance", "AmusementPark.ferrisWheel": "ferris wheel", @@ -26,7 +21,6 @@ "AmusementPark.goKarts": "go karts", "AmusementPark.label": "An amusement park scene. A scene with your favourite rides; there's a roller coaster, ferris wheel, pirate ship, go karts, swing ride and merry go round! There's other fun things to do, like a game booth and water park, and if you want some snacks, there is a snack stand as well. Your character is a train.", "AmusementPark.merryGoRound": "merry go round", - "AmusementPark.name": "Amusement Park", "AmusementPark.pirateShip": "pirate ship", "AmusementPark.rollerCoaster": "roller coaster", "AmusementPark.snackStand": "snack stand", @@ -34,16 +28,16 @@ "AmusementPark.waterPark": "water park", "AmusementPark.waterSlide": "water slide", "AmusementPark.whaleFountain": "whale fountain", - "Announcement.actionSelected": "{commandType} {command} selected", - "Announcement.add": "added {commandType} {command}", + "Announcement.actionSelected": "{actionType} {actionName} selected", + "Announcement.add": "added {actionType} {actionName}", "Announcement.backward1": "backward 1 square", "Announcement.backward2": "backward 2 squares", "Announcement.backward3": "backward 3 squares", - "Announcement.cannotMoveNext": "At the end of the program, unable to move right", - "Announcement.cannotMovePrevious": "At the beginning of the program, unable to move left", - "Announcement.cannotReplaceLoopBlocks": "replace is not available for loops", + "Announcement.cannotMoveNext": "At the end of the program, unable to move forward", + "Announcement.cannotMovePrevious": "At the beginning of the program, unable to move backward", + "Announcement.cannotReplaceLoopBlocks": "cannot replace \"beginning of loop\" or \"end of loop\" blocks", "Announcement.control": "control", - "Announcement.delete": "deleted {commandType} {command}", + "Announcement.delete": "deleted {actionType} {actionName}", "Announcement.deleteAll": "delete program?", "Announcement.endLoop": "loop {loopLabel}", "Announcement.forward1": "forward 1 square", @@ -53,24 +47,18 @@ "Announcement.left45": "turn left 45 degrees", "Announcement.left90": "turn left 90 degrees", "Announcement.loop": "loop", - "Announcement.movement": "movement", "Announcement.moveToNext": "moved to right", "Announcement.moveToPrevious": "moved to left", + "Announcement.movement": "movement", "Announcement.noActionSelected": "no action selected", - "Announcement.replace": "movement {oldCommand} replaced with {newCommand}", + "Announcement.replace": "movement {oldActionName} replaced with {newActionName}", "Announcement.right180": "turn right 180 degrees", "Announcement.right45": "turn right 45 degrees", "Announcement.right90": "turn right 90 degrees", "Announcement.startLoop": "loop {loopLabel}", "App.appHeading": "Weavly", "App.appHeading.link": "Weavly, learn more about Weavly at Weavly dot org", - "App.blockMode": "Block", - "App.changeMode": "Change Mode", - "App.privacyModalToggle": "Privacy", "App.privacyModalToggle.ariaLabel": "Weavly privacy policy", - "App.run": "Run", - "App.speechRecognition": "Speech Recognition", - "App.textMode": "Text", "AtlanticCanada.character": "Cape islander", "AtlanticCanada.fishProcessingPlant": "a fish processing plant", "AtlanticCanada.fogBank": "fog bank", @@ -79,7 +67,6 @@ "AtlanticCanada.label": "Atlantic Canada scene. Travel Atlantic Canada through the Atlantic ocean. Weather is unpredictable in the Atlantic ocean, some parts of the ocean can be stormy, and some parts of the ocean can be covered with fog. Travel safe to visit a fish processing plant, and a village on the land; you might face icebergs or whales on the way. Your character in this scene is a fishing boat, also known as a Cape Islander in Atlantic Canada.", "AtlanticCanada.land": "the land", "AtlanticCanada.lighthouse": "a light house", - "AtlanticCanada.name": "Atlantic Canada", "AtlanticCanada.rowingBoatOnTheShore": "a rowing boat on the shore", "AtlanticCanada.sailboat": "a sailboat", "AtlanticCanada.shoal": "a shoal of fish", @@ -97,14 +84,11 @@ "Camping.label": "A camping trip scene. A black bear is reaching up a tree trunk on the left side of the scene. A tree branch goes across the top of the scene and has a rope ladder hanging from it. There is an open tent on the right side of the scene. A lake and campfire are in the middle of the scene. Your character in this scene is a squirrel.", "Camping.ladder": "the rope ladder", "Camping.lake": "the lake", - "Camping.name": "Camping Trip", "Camping.tentdoor": "the tent door", "Camping.trunk": "the tree trunk", - "CharacterAriaLive.movementAriaLabel": "{character} is moving", - "CharacterDescriptionBuilder.positionAndDirection": "At {columnLabel} {rowLabel} facing {direction}", - "CharacterDescriptionBuilder.positionAndDirectionAndItem": "At {columnLabel} {rowLabel} on {item} facing {direction}", - "CharacterMessageBuilder.endOfScene": "Your character has reached the end of the scene", - "CharacterMessageBuilder.hitWall": "Your character hit a wall on {columnLabel}{rowLabel}", + "CharacterAriaLive.movementAriaLabel": "{sceneCharacter} is moving", + "CharacterDescriptionBuilder.positionAndDirection": "At {columnLabel} {rowLabel} {facingDirection}", + "CharacterDescriptionBuilder.positionAndDirectionAndItem": "At {columnLabel} {rowLabel} on {backgroundItem} {facingDirection}", "CharacterPositionController.editPosition.columnPosition": "Character column position", "CharacterPositionController.editPosition.designMode.columnPosition": "Paintbrush column position", "CharacterPositionController.editPosition.designMode.moveDown": "Move the paintbrush down", @@ -139,24 +123,6 @@ "Command.right45": "turn right 45 degrees", "Command.right90": "turn right 90 degrees", "Command.startLoop": "beginning of loop {loopLabel}", - "CommandInfo.nextStep": "after step {nextStepNumber} {command}", - "CommandInfo.nextStep.endLoop": "out of loop {loopLabel}", - "CommandInfo.nextStep.inLoop": "after step {nextStepNumber} {command} of loop {loopLabel}", - "CommandInfo.nextStep.loop": "after step {nextStepNumber} {command}", - "CommandInfo.nextStep.startLoop": "into loop {loopLabel}", - "CommandInfo.previousStep": "before step {previousStepNumber} {command}", - "CommandInfo.previousStep.endLoop": "into loop {loopLabel}", - "CommandInfo.previousStep.inLoop": "before step {previousStepNumber} {command} of loop {loopLabel}", - "CommandInfo.previousStep.loop": "before step {previousStepNumber} {command}", - "CommandInfo.previousStep.startLoop": "out of loop {loopLabel}", - "CommandPalette.controlsTitle": "Controls", - "CommandPalette.movementsTitle": "Movements", - "CommandPalette.shortMovementsTitle": "Move", - "CommandPalette.soundsTitle": "Sounds", - "ConfirmDeleteAllModal.cancelButton": "Cancel", - "ConfirmDeleteAllModal.confirmButton": "Delete", - "ConfirmDeleteAllModal.content": "Are you sure you want to delete all steps of your program?", - "ConfirmDeleteAllModal.title": "Delete Program", "CustomBackgroundDesignModeButton.customBackgroundDesignMode": "custom background design mode", "DeepOcean.babyJellyfish": "a baby jellyfish", "DeepOcean.character": "the submarine", @@ -165,22 +131,13 @@ "DeepOcean.fishGroup": "the group of fish", "DeepOcean.jellyfish": "the Jellyfish", "DeepOcean.label": "An underwater scene. A large shark circles the waters above a treasure chest at the bottom of the sea floor. The chest is filled with jewels and gold coins. Fish and jellyfish swim around coral and seaweed. Your character in this scene is a submarine.", - "DeepOcean.name": "Deep Ocean", "DeepOcean.shark": "the shark", "DeepOcean.treasure": "the treasure", "DesignModeCursorDescriptionBuilder.position": "At {columnLabel} {rowLabel}", - "DesignModeCursorDescriptionBuilder.positionAndItem": "At {columnLabel} {rowLabel} on {item}", + "DesignModeCursorDescriptionBuilder.positionAndItem": "At {columnLabel} {rowLabel} on {backgroundItem}", "DeviceConnectControl.connected": "Connected", "DeviceConnectControl.connecting": "Connecting", "DeviceConnectControl.notConnected": "Not connected", - "Direction.0": "up", - "Direction.1": "upper right", - "Direction.2": "right", - "Direction.3": "lower right", - "Direction.4": "down", - "Direction.5": "lower left", - "Direction.6": "left", - "Direction.7": "upper left", "EuropeTrip.B6": "the Tower of Saint Vincent on Portugal", "EuropeTrip.B7": "Portugal", "EuropeTrip.C1": "Iceland", @@ -189,7 +146,6 @@ "EuropeTrip.C5": "Spain", "EuropeTrip.C6": "Portugal and Spain", "EuropeTrip.C7": "Flamenco guitar on Spain", - "EuropeTrip.character": "the airplane", "EuropeTrip.D2": "Ireland and the United Kingdom", "EuropeTrip.D3": "a shamrock on Ireland; also in this square the United Kingdom", "EuropeTrip.D4": "France and the United Kingdom", @@ -250,8 +206,16 @@ "EuropeTrip.L5": "Ukraine", "EuropeTrip.L7": "Türkiye", "EuropeTrip.L8": "olives on Cyprus; also in this square Türkiye", + "EuropeTrip.character": "the airplane", "EuropeTrip.label": "A Europe trip scene containing a map of Europe with tourist attractions. Iceland is located near the top left of the scene. Cyprus is located at the bottom right of the scene. Tourist attractions are located on the country known for the attraction, such as the violin on Austria near the centre of the scene. Your character in this scene is an airplane.", - "EuropeTrip.name": "Europe Trip", + "FacingDirection.0": "facing up", + "FacingDirection.1": "facing upper right", + "FacingDirection.2": "facing right", + "FacingDirection.3": "facing lower right", + "FacingDirection.4": "facing down", + "FacingDirection.5": "facing lower left", + "FacingDirection.6": "facing left", + "FacingDirection.7": "facing upper left", "GroceryStore.apples": "apples", "GroceryStore.bagOfRice": "bag of rice", "GroceryStore.bananas": "bananas", @@ -275,7 +239,6 @@ "GroceryStore.jars": "jars", "GroceryStore.label": "A grocery store scene. In the top middle there is a refrigerator containing meat and fish. In the top right there are jars, cans, and other pantry items. On the left of the store there are breads and vegetables. In the middle towards the bottom there are fruits. And in the bottom right there is a refrigerator containing dairy and other refrigerated items. Your character in this scene is a shopping cart.", "GroceryStore.milk": "milk", - "GroceryStore.name": "Grocery Store", "GroceryStore.onions": "onions", "GroceryStore.orangeJuice": "orange juice", "GroceryStore.oranges": "oranges", @@ -296,58 +259,12 @@ "Haunted.fireplace": "the fireplace", "Haunted.label": "A spooky mansion scene. The front hall has a large staircase starting at the bottom right and going to the top left. Haunted paintings, a mirror, and a creepy deer skull hang on the wall. Bats are flying around. A fire burns in the fireplace below the stairwell. A big comfy chair is in front of the fire. The shelves of a large bookshelf on the left of the scene is stacked with books, potions and plants. Your character in this scene is a flashlight.", "Haunted.mirror": "the mirror", - "Haunted.name": "Haunted House", "Haunted.painting": "a painting", "Haunted.shelf": "the bookshelf", "Haunted.stairs": "the stairs", - "KeyboardInputModal.Cancel": "Cancel", - "KeyboardInputModal.Description.addCommandToBeginning": "Press {key} to add the selected command to the beginning of the program.", - "KeyboardInputModal.Description.addCommandToEnd": "Press {key} to add the selected command to the end of the program.", - "KeyboardInputModal.Description.announceScene": "Press {key} to announce the character position and orientation.", - "KeyboardInputModal.Description.decreaseProgramSpeed": "Press {key} to make the program play slower.", - "KeyboardInputModal.Description.deleteCurrentStep": "Press {key} to delete the currently focused step.", - "KeyboardInputModal.Description.increaseProgramSpeed": "Press {key} to make the program play faster.", - "KeyboardInputModal.Description.playPauseProgram": "Press {key} to play or pause the program.", - "KeyboardInputModal.Description.refreshScene": "Press {key} to refresh the scene.", - "KeyboardInputModal.Description.showHide": "Press {key} to show the keyboard shortcuts menu.", - "KeyboardInputModal.Description.stopProgram": "Press {key} to stop the program.", - "KeyboardInputModal.Description.toggleAnnouncements": "Press {key} to toggle announcements.", - "KeyboardInputModal.KeyIcons.A": "A", - "KeyboardInputModal.KeyIcons.Alt": "Alt", - "KeyboardInputModal.KeyIcons.B": "B", - "KeyboardInputModal.KeyIcons.Control": "Ctrl", - "KeyboardInputModal.KeyIcons.D": "D", - "KeyboardInputModal.KeyIcons.E": "E", - "KeyboardInputModal.KeyIcons.GreaterThan": ">", - "KeyboardInputModal.KeyIcons.I": "I", - "KeyboardInputModal.KeyIcons.LessThan": "<", - "KeyboardInputModal.KeyIcons.P": "P", - "KeyboardInputModal.KeyIcons.QuestionMark": "?", - "KeyboardInputModal.KeyIcons.R": "R", - "KeyboardInputModal.KeyIcons.S": "S", - "KeyboardInputModal.KeyIcons.Shift": "Shift", - "KeyboardInputModal.KeyLabels.A": "a", - "KeyboardInputModal.KeyLabels.Alt": "Alt", - "KeyboardInputModal.KeyLabels.B": "b", - "KeyboardInputModal.KeyLabels.Control": "Control", - "KeyboardInputModal.KeyLabels.D": "d", - "KeyboardInputModal.KeyLabels.E": "e", - "KeyboardInputModal.KeyLabels.GreaterThan": "greater than", - "KeyboardInputModal.KeyLabels.I": "i", - "KeyboardInputModal.KeyLabels.LessThan": "less than", - "KeyboardInputModal.KeyLabels.P": "p", - "KeyboardInputModal.KeyLabels.QuestionMark": "question mark", - "KeyboardInputModal.KeyLabels.R": "r", - "KeyboardInputModal.KeyLabels.S": "s", - "KeyboardInputModal.Save": "Save", - "KeyboardInputModal.Scheme.Descriptions.alt": "Alt (Apple: Option)", - "KeyboardInputModal.Scheme.Descriptions.controlalt": "Control+Alt (Apple: Control+Option)", "KeyboardInputModal.ShowHide.AriaLabel": "Display keyboard shortcuts menu", - "KeyboardInputModal.Title": "Keyboard Shortcuts", "KeyboardInputModal.Toggle.AriaLabel": "Keyboard shortcuts toggle", "KeyboardInputModal.Toggle.Label": "Keyboard Shortcuts", - "KeyboardInputModal.Toggle.Off": "Off", - "KeyboardInputModal.Toggle.On": "On", "Landmarks.bigBen": "Big Ben Tower", "Landmarks.burAlArab": "The Burj al Arab Building", "Landmarks.character": "the robot", @@ -361,15 +278,14 @@ "Landmarks.greatPyramid": "The Great Pyramid of Giza", "Landmarks.greatSphinx": "The Great Sphinx of Giza", "Landmarks.greatWall": "The Great Wall of China", - "Landmarks.label": "A world scene that contains 23 famous landmarks found around the world. A plane is flying from the top left corner. A train is traveling from the bottom right corner. Landmarks are located in different locations in this scene including the famous Sphinx in Egypt, Eiffel Tower in France, Tokyo Tower in Japan, and Floating Market in Vietnam. Your character to explore this scene is a robot", + "Landmarks.label": "A world scene that contains 23 famous landmarks found around the world. A plane is flying from the top left corner. A train is traveling from the bottom right corner. Landmarks are located in different locations in this scene including the famous Sphinx in Egypt, Eiffel Tower in France, Tokyo Tower in Japan, and Floating Market in Vietnam. Your character to explore this scene is a robot.", "Landmarks.leaningTowerPisa": "The Leaning Tower of Pisa", "Landmarks.machuPicchu": "Machu Picchu", - "Landmarks.name": "Landmarks", "Landmarks.niagaraFalls": "Niagara Falls", "Landmarks.operaHouse": "The Sydney Opera House", "Landmarks.plane": "a plane", - "Landmarks.statueLiberty": "The Statue of Liberty", "Landmarks.stBasils": "Saint Basil's Cathedral", + "Landmarks.statueLiberty": "The Statue of Liberty", "Landmarks.stonehenge": "Stonehenge", "Landmarks.tableMountain": "Table Mountain", "Landmarks.tajMahal": "The Taj Mahal Palace", @@ -379,14 +295,12 @@ "Marble.bricks": "bricks", "Marble.character": "the marble", "Marble.label": "A maze made of bricks in different colours. Your character in this scene is a marble. There is a path through the maze that starts where your marble is and there are multiple ways to escape the maze.", - "Marble.name": "Marble Run", "MusicBand.character": "the music note", "MusicBand.drumKit": "the drum kit", "MusicBand.guitar": "the guitar", "MusicBand.label": "A music band scene with musical instruments. The instruments are arranged in 2 rows, with one row in the middle of the scene and one towards the bottom of the scene. In the upper row, there is a guitar, a drum kit, and a saxophone. In the lower row, there is a tambourine, a xylophone with a mallet, a microphone on a stand, and a synthesizer. Across the top of the scene spotlights light the instruments. And in the lower left and right corners there are loudspeakers. Your character in this scene is a music note.", "MusicBand.loudspeaker": "a loudspeaker", "MusicBand.microphone": "the microphone on a stand", - "MusicBand.name": "Music Band", "MusicBand.saxophone": "the saxophone", "MusicBand.spotlight": "a spotlight", "MusicBand.synthesizer": "the synthesizer", @@ -396,20 +310,16 @@ "PlayButton.pause": "Pause", "PlayButton.play": "Play", "PlayControls.heading": "Play Controls", - "PrivacyModal.close": "Close", - "PrivacyModal.title": "Weavly Privacy Policy", - "ProgramBlockEditor.beginningBlock": "Add selected action {command} to the beginning of the program", - "ProgramBlockEditor.betweenBlocks": "Add selected action {command} between position {prevCommand} and position {postCommand}", + "ProgramBlockEditor.beginningBlock": "Add selected action {actionName} to the beginning of the program", + "ProgramBlockEditor.betweenBlocks": "Add selected action {actionName} between position {previousStepNumber}, {previousStepActionName} and position {nextStepNumber}, {nextStepActionName}", "ProgramBlockEditor.blocks.noCommandSelected": "Make sure an action is selected", - "ProgramBlockEditor.command": "{command}, position {index} of current program", - "ProgramBlockEditor.lastBlock": "Add selected action {command} to the end of the program", - "ProgramBlockEditor.nestedCommand": "{command}, position {index} of Loop {parentLoopLabel}", + "ProgramBlockEditor.command": "{blockName}, position {index} of current program", + "ProgramBlockEditor.lastBlock": "Add selected action {actionName} to the end of the program", + "ProgramBlockEditor.nestedCommand": "{blockName}, position {index} of Loop {parentLoopLabel}", "ProgramBlockEditor.program.deleteAll": "Delete all steps of your program", - "ProgramBlockEditor.programHeading": "Program", - "ProgramBlockEditor.toggleAddNodeExpandMode": "add node expanded mode", + "ProgramBlockEditor.toggleAddNodeExpandMode": "show \"add\" buttons", "ProgramSequence.heading": "Program Sequence", "ProgramSpeedController.slider": "Program play speed", - "ProgramTextEditor.programLabel": "Program:", "RefreshButton": "Refresh", "Savannah.alligator": "the Alligator", "Savannah.babyAlligator": "the Baby Alligator", @@ -421,29 +331,13 @@ "Savannah.hippo": "the Hippo", "Savannah.label": "A savannah scene. A lion roars at the top of a cliff above the horizon. A mother and baby giraffe roam the savannah. Two crocodiles, a flamingo and a hippopotamus drink water from a pond surrounded by trees. Your character in this scene is a Jeep.", "Savannah.lion": "the Lion", - "Savannah.name": "Savannah", "Savannah.pond": "the Pond", "Savannah.tree": "a Tree", - "Scene.description": "Scene, in {world}, {numColumns} by {numRows} grid. {characterDescription}", + "Scene.description": "Scene, in {backgroundName}, {numColumns} by {numRows} grid. {characterDescription}", "Scene.heading": "Scene", "SceneMessage.close": "close message", - "ShareButton": "Share", - "ShareModal.close": "Close", - "ShareModal.copy": "Copy link", - "ShareModal.description1": "A link to the program you created was copied to the clipboard.", - "ShareModal.description2": "You can also copy the link below to share it with anyone you like.", - "ShareModal.title": "Share Link", "Sketchpad.character": "the robot", "Sketchpad.label": "A blank sketchbook with grid lines. Your character in this scene is a Robot.", - "Sketchpad.name": "Sketchpad", - "SoundOptionsModal.allSounds": "All Sounds", - "SoundOptionsModal.announcements": "Audio Announcements", - "SoundOptionsModal.cancelButton": "Cancel", - "SoundOptionsModal.musicalSounds": "Musical Sounds", - "SoundOptionsModal.saveButton": "Save", - "SoundOptionsModal.title": "Sound Options", - "SoundOptionsModal.toggleOff": "Off", - "SoundOptionsModal.toggleOn": "On", "Space.aliens": "the Aliens", "Space.asteroid": "an asteroid", "Space.character": "the spaceship", @@ -452,7 +346,6 @@ "Space.mars": "Mars", "Space.meteor": "a Meteor", "Space.moon": "the Moon", - "Space.name": "Space", "Space.satellite": "the satellite", "Space.saturn": "Saturn", "Space.star": "a star", @@ -470,9 +363,8 @@ "Sports.golfBall": "golf ball", "Sports.hockeyStickAndPuck": "hockey stick and puck", "Sports.iceSkates": "ice skates", - "Sports.label": "A sports scene with 23 different pieces of sporting equipment spread across the scene. Your character is a golf cart", + "Sports.label": "A sports scene with 23 different pieces of sporting equipment spread across the scene. Your character is a golf cart.", "Sports.martialArtsUniform": "martial arts uniform", - "Sports.name": "Sports", "Sports.rowingBoat": "rowing boat", "Sports.runningShoes": "running shoes", "Sports.singlet": "singlet", @@ -483,15 +375,7 @@ "Sports.tennisRacketAndBall": "tennis racket and ball", "Sports.volleyballBall": "volleyball ball", "StopButton": "Stop", - "ThemeSelector.cancelButton": "Cancel", "ThemeSelector.iconButton": "Theme Selector", - "ThemeSelector.option.contrast": "High Contrast", - "ThemeSelector.option.dark": "Dark", - "ThemeSelector.option.default": "Default", - "ThemeSelector.option.gray": "Grayscale", - "ThemeSelector.option.light": "Light", - "ThemeSelector.saveButton": "Save", - "ThemeSelector.title": "Themes", "TileDescription.black": "black", "TileDescription.brown": "brown", "TileDescription.darkBlue": "dark blue", @@ -510,32 +394,661 @@ "TileDescription.white": "white", "TileDescription.yellow": "yellow", "TilePanel.heading": "Custom Background Design", - "WorldSelector.Cancel": "Cancel", - "WorldSelector.Prompt": "Select a background for your scene.", - "WorldSelector.Save": "Save", - "WorldSelector.Title": "Scene Background", + "UI.ActionsMenu.title": "Actions", + "UI.ActionsMenuItem.command.backward1": "Move backward 1 step", + "UI.ActionsMenuItem.command.forward1": "Move forward 1 step", + "UI.ActionsMenuItem.command.left45": "Turn left 45 degrees", + "UI.ActionsMenuItem.command.left90": "Turn left 90 degrees", + "UI.ActionsMenuItem.command.loop": "Loop", + "UI.ActionsMenuItem.command.right45": "Turn right 45 degrees", + "UI.ActionsMenuItem.command.right90": "Turn right 90 degrees", + "UI.ActionsMenuItem.usedItemToggleLabel": "(Used)", + "UI.ActionsSimplificationModal.title": "Available Actions", + "UI.AmusementPark.name": "Amusement Park", + "UI.App.privacyModalToggle": "Privacy", + "UI.AtlanticCanada.name": "Atlantic Canada", + "UI.Camping.name": "Camping Trip", + "UI.Cancel": "Cancel", + "UI.CharacterMessageBuilder.endOfScene": "Your character has reached the end of the scene", + "UI.CharacterMessageBuilder.hitWall": "Your character hit a wall on {columnLabel}{rowLabel}", + "UI.Close": "Close", + "UI.CommandPalette.controlsTitle": "Controls", + "UI.CommandPalette.movementsTitle": "Movements", + "UI.ConfirmDeleteAllModal.confirmButton": "Delete", + "UI.ConfirmDeleteAllModal.content": "Are you sure you want to delete all steps of your program?", + "UI.ConfirmDeleteAllModal.title": "Delete Program", + "UI.DeepOcean.name": "Deep Ocean", + "UI.EuropeTrip.name": "Europe Trip", + "UI.GroceryStore.name": "Grocery Store", + "UI.Haunted.name": "Haunted House", + "UI.KeyboardInputModal.Description.addCommandToBeginning": "Press {keyboardShortcut} to add the selected command to the beginning of the program.", + "UI.KeyboardInputModal.Description.addCommandToEnd": "Press {keyboardShortcut} to add the selected command to the end of the program.", + "UI.KeyboardInputModal.Description.announceScene": "Press {keyboardShortcut} to announce the character position and orientation.", + "UI.KeyboardInputModal.Description.decreaseProgramSpeed": "Press {keyboardShortcut} to make the program play slower.", + "UI.KeyboardInputModal.Description.deleteCurrentStep": "Press {keyboardShortcut} to delete the currently focused step.", + "UI.KeyboardInputModal.Description.increaseProgramSpeed": "Press {keyboardShortcut} to make the program play faster.", + "UI.KeyboardInputModal.Description.playPauseProgram": "Press {keyboardShortcut} to play or pause the program.", + "UI.KeyboardInputModal.Description.refreshScene": "Press {keyboardShortcut} to refresh the scene.", + "UI.KeyboardInputModal.Description.showHide": "Press {keyboardShortcut} to show the keyboard shortcuts menu.", + "UI.KeyboardInputModal.Description.stopProgram": "Press {keyboardShortcut} to stop the program.", + "UI.KeyboardInputModal.Description.toggleAnnouncements": "Press {keyboardShortcut} to toggle announcements.", + "UI.KeyboardInputModal.KeyIcons.A": "A", + "UI.KeyboardInputModal.KeyIcons.Alt": "Alt", + "UI.KeyboardInputModal.KeyIcons.B": "B", + "UI.KeyboardInputModal.KeyIcons.Control": "Ctrl", + "UI.KeyboardInputModal.KeyIcons.D": "D", + "UI.KeyboardInputModal.KeyIcons.E": "E", + "UI.KeyboardInputModal.KeyIcons.GreaterThan": ">", + "UI.KeyboardInputModal.KeyIcons.I": "I", + "UI.KeyboardInputModal.KeyIcons.LessThan": "<", + "UI.KeyboardInputModal.KeyIcons.P": "P", + "UI.KeyboardInputModal.KeyIcons.QuestionMark": "?", + "UI.KeyboardInputModal.KeyIcons.R": "R", + "UI.KeyboardInputModal.KeyIcons.S": "S", + "UI.KeyboardInputModal.KeyIcons.Shift": "Shift", + "UI.KeyboardInputModal.KeyLabels.A": "a", + "UI.KeyboardInputModal.KeyLabels.Alt": "Alt", + "UI.KeyboardInputModal.KeyLabels.B": "b", + "UI.KeyboardInputModal.KeyLabels.Control": "Control", + "UI.KeyboardInputModal.KeyLabels.D": "d", + "UI.KeyboardInputModal.KeyLabels.E": "e", + "UI.KeyboardInputModal.KeyLabels.GreaterThan": "greater than", + "UI.KeyboardInputModal.KeyLabels.I": "i", + "UI.KeyboardInputModal.KeyLabels.LessThan": "less than", + "UI.KeyboardInputModal.KeyLabels.P": "p", + "UI.KeyboardInputModal.KeyLabels.QuestionMark": "question mark", + "UI.KeyboardInputModal.KeyLabels.R": "r", + "UI.KeyboardInputModal.KeyLabels.S": "s", + "UI.KeyboardInputModal.Scheme.Descriptions.alt": "Alt (Apple: Option)", + "UI.KeyboardInputModal.Scheme.Descriptions.controlalt": "Control+Alt (Apple: Control+Option)", + "UI.KeyboardInputModal.Title": "Keyboard Shortcuts", + "UI.Landmarks.name": "Landmarks", + "UI.Marble.name": "Marble Run", + "UI.MusicBand.name": "Music Band", + "UI.Off": "Off", + "UI.On": "On", + "UI.PrivacyModal.contactUs": "contact us", + "UI.PrivacyModal.section010.Heading": "Updated January 4th, 2024", + "UI.PrivacyModal.section010.block010": "At Weavly, we believe that privacy is a fundamental human right, and acknowledge how important it is to our community—especially with regards to both children and parents.", + "UI.PrivacyModal.section010.block020": "This page explains:", + "UI.PrivacyModal.section010.block030.item010": "What type of information we store on our website (http://create.weavly.org/)", + "UI.PrivacyModal.section010.block030.item020": "How that information is used and processed", + "UI.PrivacyModal.section010.block030.item030": "How we keep your information safe", + "UI.PrivacyModal.section020.Heading": "What information does Weavly store?", + "UI.PrivacyModal.section020.block010": "As you use Weavly, we may store information on how Weavly is accessed and used by you. We store the following information independently on every browser/device pair that you use Weavly on:", + "UI.PrivacyModal.section020.block020.item010": "Your prefered settings for display colour theme and keyboard shortcuts", + "UI.PrivacyModal.section020.block020.item020": "Your visible set of action blocks on the action panel", + "UI.PrivacyModal.section020.block020.item030": "Your selected background for the scene", + "UI.PrivacyModal.section020.block020.item040": "Your created program", + "UI.PrivacyModal.section020.block020.item050": "Any line that is drawn on the scene as a result of running your program", + "UI.PrivacyModal.section020.block020.item060": "The last position of your character on the scene", + "UI.PrivacyModal.section020.block020.item070": "The starting position of your character", + "UI.PrivacyModal.section020.block020.item080": "The version of Weavly that was used", + "UI.PrivacyModal.section020.block030": "You can delete any information that Weavly has generated from your usage by clearing your browser's cache and local storage. Check your browser's documentation for details.", + "UI.PrivacyModal.section030.Heading": "How does Weavly use this information?", + "UI.PrivacyModal.section030.block010": "The generated information is kept in a local storage on your device to make your use of Weavly more convenient:", + "UI.PrivacyModal.section030.block020.item010": "Your settings for the coding environment are stored so you don't have to adjust them every time you launch Weavly", + "UI.PrivacyModal.section030.block020.item020": "If you happen to accidentally or intentionally close your browser, the coding environment will be the same as when you left it for the next time you launch Weavly", + "UI.PrivacyModal.section030.block030": "Although storing your information in the browser makes it convenient for every time you access Weavly, it may cause problems on shared computers. As a result, someone that uses the computer after you may be able to access your Weavly settings and program.", + "UI.PrivacyModal.section040.Heading": "How do we keep your information safe?", + "UI.PrivacyModal.section040.block010": "The security of your data is important to us, but please keep in mind that no method of electronic storage is 100% secure. We currently use browser local storage to store the specified data. The information is stored in the browser by domain (create.weavly.org) and only code running from that domain may access it. Thus, other websites cannot access the data. However, local storage is not encrypted on disk and someone with access to the device could get access to the data.", + "UI.PrivacyModal.section050.Heading": "Children's Privacy", + "UI.PrivacyModal.section050.block010": "We do not knowingly collect personally identifiable information from anyone under the age of 18. If you are a parent or guardian and you have become aware that we have collected Personal Data from your children without verification of parental consent, we can take steps to remove that information from our servers. Please {contactLink}", + "UI.PrivacyModal.section060.Heading": "Changes to This Privacy Policy", + "UI.PrivacyModal.section060.block010": "We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page. We will let you know of any changes becoming effective by updating the “effective date” at the top of this Privacy Policy. You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.", + "UI.PrivacyModal.section070.Heading": "Contact Us", + "UI.PrivacyModal.section070.block010": "If you have any questions about this Privacy Policy, please {contactLink}.", + "UI.PrivacyModal.title": "Weavly Privacy Policy", + "UI.ProgramBlockEditor.programHeading": "Program", + "UI.Savannah.name": "Savannah", + "UI.Save": "Save", + "UI.ShareButton": "Share", + "UI.ShareModal.copy": "Copy link", + "UI.ShareModal.description1": "A link to the program you created was copied to the clipboard.", + "UI.ShareModal.description2": "You can also copy the link below to share it with anyone you like.", + "UI.ShareModal.title": "Share Link", + "UI.Sketchpad.name": "Sketchpad", + "UI.SoundOptionsModal.allSounds": "All Sounds", + "UI.SoundOptionsModal.announcements": "Audio Announcements", + "UI.SoundOptionsModal.musicalSounds": "Musical Sounds", + "UI.SoundOptionsModal.title": "Sound Options", + "UI.Space.name": "Space", + "UI.Sports.name": "Sports", + "UI.ThemeSelector.option.contrast": "High Contrast", + "UI.ThemeSelector.option.dark": "Dark", + "UI.ThemeSelector.option.default": "Default", + "UI.ThemeSelector.option.gray": "Grayscale", + "UI.ThemeSelector.option.light": "Light", + "UI.ThemeSelector.title": "Themes", + "UI.WorldSelector.Prompt": "Select a background for your scene.", + "UI.WorldSelector.Title": "Scene Background", "WorldSelectorButton.heading": "Scene Background selector", "WorldSelectorButton.label": "Scene Background selector" }, "fr": { - "App.blockMode": "Block in French", - "App.changeMode": "Change Mode in French", - "App.run": "Lancer", - "App.speechRecognition": "Speech Recognition in French", - "App.textMode": "Text in French", - "CommandPalette.movementsTitle": "Movements in French", - "CommandPalette.soundsTitle": "Sounds in French", - "CommandPaletteCommand.forward": "Forward in French", - "CommandPaletteCommand.left": "Left in French", - "CommandPaletteCommand.right": "Right in French", + "ActionPanel.action.delete": "supprimer l'étape {stepNumber} {stepActionName}", + "ActionPanel.action.moveToNextStep": "déplacer l'étape {stepNumber} {stepActionName} après l'étape {nextStepNumber} {nextStepActionName}", + "ActionPanel.action.moveToNextStep.disabled": "déplacer l'étape {stepNumber} {stepActionName}", + "ActionPanel.action.moveToNextStep.intoLoop": "déplacer l'étape {stepNumber} {stepActionName} dans la boucle {loopLabel}", + "ActionPanel.action.moveToNextStep.outOfLoop": "sortir l’étape {stepNumber} {stepActionName} de la boucle {loopLabel}", + "ActionPanel.action.moveToNextStep.withinLoop": "déplacer l'étape {stepNumber} {stepActionName} après l'étape {nextStepNumber} {nextStepActionName} de la boucle {loopLabel}", + "ActionPanel.action.moveToPreviousStep": "déplacer l'étape {stepNumber} {stepActionName} avant l'étape {nextStepNumber} {nextStepActionName}", + "ActionPanel.action.moveToPreviousStep.disabled": "déplacer l'étape {stepNumber} {stepActionName}", + "ActionPanel.action.moveToPreviousStep.intoLoop": "déplacer l'étape {stepNumber} {stepActionName} dans la boucle {loopLabel}", + "ActionPanel.action.moveToPreviousStep.outOfLoop": "sortir l’étape {stepNumber} {stepActionName} de la boucle {loopLabel}", + "ActionPanel.action.moveToPreviousStep.withinLoop": "déplacer l'étape {stepNumber} {stepActionName} avant l'étape {previousStepNumber} {previousStepActionName} de la boucle {loopLabel}", + "ActionPanel.action.replace.noSelectedAction": "remplacer l'étape {stepNumber} {stepActionName}", + "ActionPanel.action.replace.withSelectedAction": "remplacer l'étape {stepNumber} {stepActionName} par l'action sélectionnée {selectedActionName}", + "ActionsMenu.toggleActionsMenu": "configurer les actions disponibles", + "AmusementPark.character": "le train", + "AmusementPark.entrance": "entrée", + "AmusementPark.ferrisWheel": "grande roue", + "AmusementPark.gameBooth": "kiosque de jeu", + "AmusementPark.goKarts": "karts", + "AmusementPark.label": "Une scène de parc d'attractions. Une scène avec vos manèges préférées; il y a des montagnes rousses, une grande roue, un bateau pirate, des karts, une balançoire, et un manège ! Il y a d'autres choses amusantes à faire, comme un kiosque de jeux et un parc aquatique, et si vous voulez des collations, il y a aussi un kiosque de collations.Ton personnage est un train.", + "AmusementPark.merryGoRound": "manège", + "AmusementPark.pirateShip": "navire pirate", + "AmusementPark.rollerCoaster": "montagnes rousses", + "AmusementPark.snackStand": "kiosque de collations", + "AmusementPark.swingRide": "balançoire", + "AmusementPark.waterPark": "parc aquatique", + "AmusementPark.waterSlide": "glissade d'eau", + "AmusementPark.whaleFountain": "fontaine des baleines", + "Announcement.actionSelected": "{actionType} {actionName} sélectionné", + "Announcement.add": "{actionType} {actionName} ajouté", + "Announcement.backward1": "reculer d'un carré", + "Announcement.backward2": "reculer de 2 carrés", + "Announcement.backward3": "reculer de 3 carrés", + "Announcement.cannotMoveNext": "à la fin du programme, incapable d'avancer", + "Announcement.cannotMovePrevious": "au début du programme, incapable de reculer", + "Announcement.cannotReplaceLoopBlocks": "ne peut pas remplacer les blocs « début de boucle » ou « fin de boucle »", + "Announcement.control": "contrôle", + "Announcement.delete": "{actionType} {actionName} supprimé", + "Announcement.deleteAll": "suprimmer le programme ?", + "Announcement.endLoop": "boucle {loopLabel}", + "Announcement.forward1": "avancer d'un carré", + "Announcement.forward2": "avancer de 2 carrés", + "Announcement.forward3": "avancer de 3 carrés", + "Announcement.left180": "tourner à gauche à 180 degrés", + "Announcement.left45": "tourner à gauche à 45 degrés", + "Announcement.left90": "tourner à gauche à 90 degrés", + "Announcement.loop": "boucle", + "Announcement.moveToNext": "déplacé vers la droite", + "Announcement.moveToPrevious": "déplacé vers la gauche", + "Announcement.movement": "mouvement", + "Announcement.noActionSelected": "aucune action sélectionnée", + "Announcement.replace": "mouvement {oldActionName} remplacé par {newActionName}", + "Announcement.right180": "tourner à droite à 180 degrés", + "Announcement.right45": "tourner à droite à 45 degrés", + "Announcement.right90": "tourner à droite à 90 degrés", + "Announcement.startLoop": "boucle {loopLabel}", + "App.appHeading": "Weavly", + "App.appHeading.link": "Weavly, en savoir plus sur Weavly sur Weavly point org", + "App.privacyModalToggle.ariaLabel": "politique de confidentialité de Weavly", + "AtlanticCanada.character": "Cape Islander", + "AtlanticCanada.fishProcessingPlant": "usine de transformation du poisson", + "AtlanticCanada.fogBank": "banc de brouillard", + "AtlanticCanada.house": "une maison", + "AtlanticCanada.iceberg": "un iceberg", + "AtlanticCanada.label": "Scène du Canada atlantique. Parcourez le Canada atlantique à travers l'océan Atlantique. La temps est imprévisable dans l'océan Atlantique, certaines parties de l'océan peuvent être orageuses et certaines parties de l'océan peuvent être couvertes de brouillard. Voyagez en toute sécurité pour visiter une usine de transformation du poisson et une village sur terre ; vous pourriez rencontrer des icebergs ou des baleine en chemin. Votre personnage dans cette scène est un bateau de pêche, également connu sous le nom de Cape Islander dans le Canada atlantique.", + "AtlanticCanada.land": "la terre", + "AtlanticCanada.lighthouse": "un phare", + "AtlanticCanada.rowingBoatOnTheShore": "un bateau à rames sur le rivage", + "AtlanticCanada.sailboat": "un voilier", + "AtlanticCanada.shoal": "un banc de poissons", + "AtlanticCanada.shore": "le rivage", + "AtlanticCanada.storms": "tempêtes", + "AtlanticCanada.trees": "arbres", + "AtlanticCanada.water": "l'eau", + "AtlanticCanada.whale": "une baleine", + "BluetoothApiWarning.errorIconLabel": "avertissement", + "BluetoothApiWarning.message": "La connexion du robot Dash ne fonctionne actuellement que dans Chrome.", + "Camping.bear": "l'ours noire", + "Camping.branch": "la branche d'arbre", + "Camping.character": "l'écureuil", + "Camping.fire": "le feu de camp", + "Camping.label": "Une scène de camping. Un ours noir grimpe sur un tronc d'arbre sur le côté gauche de la scène. Une branche d'arbre traverse le haut de la scène et une échelle de corde y est suspendue. Il y a une tente ouverte sur le côté droit de la scène. Un lac et un feu de camp se trouvent au milieu de la scène. Votre personnage dans cette scène est un écureuil.", + "Camping.ladder": "une échelle de corde", + "Camping.lake": "le lac", + "Camping.tentdoor": "la porte de la tente", + "Camping.trunk": "le tronc d'arbre", + "CharacterAriaLive.movementAriaLabel": "{sceneCharacter} bouge", + "CharacterDescriptionBuilder.positionAndDirection": "À {columnLabel} {rowLabel} {facingDirection}", + "CharacterDescriptionBuilder.positionAndDirectionAndItem": "À {columnLabel} {rowLabel} sur {backgroundItem} {facingDirection}", + "CharacterPositionController.editPosition.columnPosition": "position de la colonne de charactères", + "CharacterPositionController.editPosition.designMode.columnPosition": "position de la colonne du pinceau", + "CharacterPositionController.editPosition.designMode.moveDown": "déplacer le pinceau vers le bas", + "CharacterPositionController.editPosition.designMode.moveLeft": "déplacer le pinceau vers la gauche", + "CharacterPositionController.editPosition.designMode.moveRight": "déplacer le pinceau vers la droite", + "CharacterPositionController.editPosition.designMode.moveUp": "déplacer le pinceau vers le haut", + "CharacterPositionController.editPosition.designMode.rowPosition": "position de la rangée du pinceau", + "CharacterPositionController.editPosition.moveDown": "déplacer le personnage vers le bas", + "CharacterPositionController.editPosition.moveLeft": "déplacer le personnage vers la gauche", + "CharacterPositionController.editPosition.moveRight": "déplacer le personnage vers la droite", + "CharacterPositionController.editPosition.moveUp": "déplacer le personnage vers le haut", + "CharacterPositionController.editPosition.rowPosition": "position de la rangée du personnage", + "CharacterPositionController.editPosition.turnLeft": "tournez le personnage à gauche", + "CharacterPositionController.editPosition.turnRight": "tournez le personnage à droite", + "CharacterPositionController.paintbrushButtonEraserSelected": "effacer le carré", + "CharacterPositionController.paintbrushButtonNoSelection": "peindre un carré de fond", + "CharacterPositionController.paintbrushButtonTileSelected": "peindre {tile}", + "CharacterPositionController.setStartButton": "définir la position de départ", + "Command.backward1": "reculer d'un carré", + "Command.backward2": "reculer de 2 carrés", + "Command.backward3": "reculer de 3 carrés", + "Command.endLoop": "fin de boucle {loopLabel}", + "Command.forward1": "avancer d'un carré", + "Command.forward2": "avancer de 2 carrés", + "Command.forward3": "avancer de 3 carrés", + "Command.left180": "tourner à gauche à 180 degrés", + "Command.left45": "tourner à gauche à 45 degrés", + "Command.left90": "tourner à gauche à 90 degrés", + "Command.loop": "boucle", + "Command.loop.label": "boucle {loopLabel}", + "Command.right180": "tourner à droite à 180 degrés", + "Command.right45": "tourner à droite à 45 degrés", + "Command.right90": "tourner à droite à 90 degrés", + "Command.startLoop": "début de boucle {loopLabel}", + "CustomBackgroundDesignModeButton.customBackgroundDesignMode": "mode de conception d'arrière-plan personnalisé", + "DeepOcean.babyJellyfish": "un bébé méduse", + "DeepOcean.character": "le sous-marin", + "DeepOcean.coral": "corail", + "DeepOcean.fish": "un poisson", + "DeepOcean.fishGroup": "le groupe de poissons", + "DeepOcean.jellyfish": "le méduse", + "DeepOcean.label": "Une scène sous-marine. Un grand requin fait le tour des eaux au-dessus d'un coffre au trésor au fond de la mer. Le coffre est rempli de bijoux et de pièces d'or. Poissons et méduses nagent autour des coraux et des algues. Ton personnage dans cette scène est un sous-marin.", + "DeepOcean.shark": "le requin", + "DeepOcean.treasure": "le trésor", + "DesignModeCursorDescriptionBuilder.position": "À {columnLabel} {rowLabel}", + "DesignModeCursorDescriptionBuilder.positionAndItem": "À {columnLabel} {rowLabel} sur {backgroundItem}", "DeviceConnectControl.connected": "Connecté", "DeviceConnectControl.connecting": "Connexion en cours", "DeviceConnectControl.notConnected": "Pas connecté", - "ProgramBlockEditor.command.forward": "Forward, position {index} of current program in French", - "ProgramBlockEditor.command.left": "Left, position {index} of current program in French", - "ProgramBlockEditor.command.none": "Empty block, position {index} of current program in French", - "ProgramBlockEditor.command.right": "Right, position {index} of current program in French", - "ProgramBlockEditor.editorAction.clear": "Clear and start new program in French", - "ProgramTextEditor.programLabel": "Logiciel:" + "EuropeTrip.B6": "la Tour de Saint Vincent au Portugal", + "EuropeTrip.B7": "Portugal", + "EuropeTrip.C1": "Islande", + "EuropeTrip.C2": "une baleine en Islande", + "EuropeTrip.C3": "Irelande", + "EuropeTrip.C5": "Espagne", + "EuropeTrip.C6": "Portugal et Espagne", + "EuropeTrip.C7": "Guitare flamenco en Espagne", + "EuropeTrip.D2": "Irlande et Royaume-Uni", + "EuropeTrip.D3": "un trèfle sur l'Irlande ; aussi sur cette place le Royaume-Uni", + "EuropeTrip.D4": "France et Royaume-Uni", + "EuropeTrip.D5": "France", + "EuropeTrip.D6": "France et Espagne", + "EuropeTrip.D7": "Espagne", + "EuropeTrip.E1": "le Royaume-Uni", + "EuropeTrip.E2": "le Royaume-Uni", + "EuropeTrip.E3": "un autobus à deux étages au Royaume-Uni", + "EuropeTrip.E4": "chocolat en Belgique; aussi sur cette place la France et le Royaume-Uni", + "EuropeTrip.E5": "La Tour Eiffel en France", + "EuropeTrip.E6": "La Tour Eiffel en France", + "EuropeTrip.E7": "Espagne", + "EuropeTrip.F1": "Norvège", + "EuropeTrip.F2": "un drakkar sur la Norvège", + "EuropeTrip.F3": "un moulin à vent aux Pays-Bas", + "EuropeTrip.F4": "un bretzel sur l'Allemagne ; également sur cette place Belgique et Luxembourg et Pays-Bas", + "EuropeTrip.F5": "une surveillance de la Suisse ; également sur cette place Autriche et France et Allemagne", + "EuropeTrip.F6": "France et Italie", + "EuropeTrip.F7": "France et Italie", + "EuropeTrip.G1": "Norvège et Suède", + "EuropeTrip.G2": "Danemark and Norvège et Suède", + "EuropeTrip.G3": "Pâtisserie danoise au Danemark ; aussi sur cette place l'Allemagne et la Suède", + "EuropeTrip.G4": "Colonnade du moulin en République tchèque ; aussi sur cette place l'Allemagne et la Pologne", + "EuropeTrip.G5": "un violon sur l'Autriche et Bled sur la Slovénie ; également sur cette place en République tchèque et en Allemagne", + "EuropeTrip.G6": "le miel en Croatie et Bled en Slovénie ; aussi sur cette place Italie", + "EuropeTrip.G7": "pizza en Italie", + "EuropeTrip.G8": "Italie", + "EuropeTrip.H1": "Suède", + "EuropeTrip.H2": "Galma Stan en Suède", + "EuropeTrip.H3": "Pologne et Suède", + "EuropeTrip.H4": "Kielbasa sur la Pologne", + "EuropeTrip.H5": "le paprika en Hongrie et la céramique en Slovaquie ; également sur cette place Autriche et la République tchèque et Slovénie", + "EuropeTrip.H6": "la mosquée Sinan Pacha au Kosovo et l'église Saint-Sava en Serbie et Stari Most en Bosnie-Herzégovine ; aussi sur cette place la Croatie et la Hongrie et le Monténégro", + "EuropeTrip.H7": "une chargia en Albanie et la mosquée Sinan Pacha au Kosovo et un yacht au Monténégro ; également sur cette place Macédoine du Nord", + "EuropeTrip.H8": "Grèce et Italie", + "EuropeTrip.I1": "La cathédrale d'Helsinki est la Finlande", + "EuropeTrip.I2": "Tallinn en Estonie ; également sur cette place la Finlande et la Lettonie", + "EuropeTrip.I3": "des marguerites en Lettonie et le château de l'île de Trakai en Lituanie ; aussi sur cette place Biélorussie", + "EuropeTrip.I4": "Biéroussie et Pologne et Ukraine", + "EuropeTrip.I5": "Château de Peles en Roumanie ; également sur cette place la Hongrie et la Slovaquie et l'Ukraine", + "EuropeTrip.I6": "Église Saint-Sava en Serbie ; aussi sur cette place la Bulgarie et la Roumanie", + "EuropeTrip.I7": "Vallée Rose en Bulgarie et Millennium Cross en Macédoine du Nord ; également sur cette place la Grèce et le Kosovo", + "EuropeTrip.I8": "le Parthénon sur la Grèce", + "EuropeTrip.J1": "Finlande", + "EuropeTrip.J3": "Bibliothèque nationale sur la Biélorussie", + "EuropeTrip.J4": "Biélorussie et Ukraine", + "EuropeTrip.J5": "tournesol en Moldavie ; aussi sur cette place la Roumanie et l'Ukraine", + "EuropeTrip.J6": "Bulgaria et Moldova et Romanie", + "EuropeTrip.J7": "Bulgarie et Turquie", + "EuropeTrip.J8": "Grèce et Turquie", + "EuropeTrip.K4": "Ukraine", + "EuropeTrip.K5": "Monastère au dôme doré de Saint-Michel en Ukraine", + "EuropeTrip.K6": "Ukraine", + "EuropeTrip.K7": "café en Turquie", + "EuropeTrip.K8": "olives à Chypre; aussi sur cette place Türkiye", + "EuropeTrip.L4": "Ukraine", + "EuropeTrip.L5": "Ukraine", + "EuropeTrip.L7": "Turquie", + "EuropeTrip.L8": "olives à Chypre; aussi sur cette place Türkiye", + "EuropeTrip.character": "l'avion", + "EuropeTrip.label": "Une scène de voyage en Europe contenant une carte de l'Europe avec des attractions touristiques. L'Islande est située en haut à gauche de la scène. Chypre est située en bas à droite de la scène. Les attractions touristiques sont situées dans le pays connu pour l'attraction, comme le violon en Autriche, près du centre de la scène. Ton personnage dans cette scène est un avion.", + "FacingDirection.0": "face vers le haut", + "FacingDirection.1": "en haut à droite", + "FacingDirection.2": "face à droite", + "FacingDirection.3": "en bas à droite", + "FacingDirection.4": "face vers le bas", + "FacingDirection.5": "en bas à gauche", + "FacingDirection.6": "face à gauche", + "FacingDirection.7": "en haut à gauche", + "GroceryStore.apples": "pommes", + "GroceryStore.bagOfRice": "sac de riz", + "GroceryStore.bananas": "bananes", + "GroceryStore.bottles": "bouteilles", + "GroceryStore.bread": "pain", + "GroceryStore.broccoli": "brocoli", + "GroceryStore.cans": "canettes", + "GroceryStore.carrots": "carottes", + "GroceryStore.ceilingLight": "plafonnier", + "GroceryStore.character": "le panier", + "GroceryStore.cheese": "fromage", + "GroceryStore.chicken": "poulet", + "GroceryStore.chocolateMilk": "lait au chocolat", + "GroceryStore.cucumbers": "concombres", + "GroceryStore.eggplants": "aubergines", + "GroceryStore.eggs": "oeufs", + "GroceryStore.fish": "poisson", + "GroceryStore.grapes": "raisins", + "GroceryStore.greenVegetables": "légumes verts", + "GroceryStore.groundBeef": "boeuf haché", + "GroceryStore.jars": "pots", + "GroceryStore.label": "Une scène d'épicerie. En haut au centre se trouve un frigo contenant de la viande et du poisson. En haut à droite se trouvent des pots, des canettes et d'autres articles de garde-manger. À gauche du magasin se trouvent des pains et des légumes. Au milieu vers le bas se trouvent des fruits. Et en bas à droite se trouve un frigo avec des produits laitiers et d'autres produits réfrigérés. Ton personnage dans cette scène est un caddie.", + "GroceryStore.milk": "lait", + "GroceryStore.onions": "onions", + "GroceryStore.orangeJuice": "jus d'orange", + "GroceryStore.oranges": "oranges", + "GroceryStore.pasta": "pâtes", + "GroceryStore.pears": "poires", + "GroceryStore.pineapples": "ananas", + "GroceryStore.potatoes": "patates", + "GroceryStore.refrigerator": "frigo", + "GroceryStore.steak": "steak", + "GroceryStore.strawberries": "fraises", + "GroceryStore.tofu": "tofu", + "GroceryStore.tomatoes": "tomates", + "GroceryStore.watermelons": "melons d'eau", + "GroceryStore.yogurt": "yogourt", + "Haunted.chair": "la chaise", + "Haunted.character": "la lampe de poche", + "Haunted.deerSkull": "le crâne de chevreuil", + "Haunted.fireplace": "la cheminée", + "Haunted.label": "Une scène de manoir effrayante. Le hall d'entrée comporte un grand escalier partant du bas à droite et allant en haut à gauche. Des peintures hantées, un miroir et un crâne de cerf effrayant sont accrochés au mur. Les chauves-souris volent. Un feu brûle dans la cheminée sous la cage d'escalier. Une grande chaise confortable est devant le feu. Les tablettes d'une grande bibliothèque à gauche de la scène sont remplies de livres, de potions et de plantes. Votre personnage dans cette scène est une lampe de poche.", + "Haunted.mirror": "le miroir", + "Haunted.painting": "une peinture", + "Haunted.shelf": "la bibliothèque", + "Haunted.stairs": "les escaliers", + "KeyboardInputModal.ShowHide.AriaLabel": "afficher le menu des raccourcis claviers", + "KeyboardInputModal.Toggle.AriaLabel": "Basculement des raccourcis clavier", + "KeyboardInputModal.Toggle.Label": "Raccourcis clavier", + "Landmarks.bigBen": "Tour Big Ben", + "Landmarks.burAlArab": "Le bâtiment Burj al Arab", + "Landmarks.character": "le robot", + "Landmarks.cnTower": "La Tour CN", + "Landmarks.colosseum": "Le Colisée", + "Landmarks.easterIsland": "Les statues de l'île de Pâques", + "Landmarks.eiffelTower": "La Tour Eiffel", + "Landmarks.fairyChimneys": "Cheminées de fées", + "Landmarks.floatingMarket": "Le marché flottant du Vietnam", + "Landmarks.grandCanyon": "Le Grand Canyon", + "Landmarks.greatPyramid": "La Grande Pyramide de Gizeh", + "Landmarks.greatSphinx": "Le Grand Sphinx de Gizeh", + "Landmarks.greatWall": "La Grande Muraille de Chine", + "Landmarks.label": "Une scène mondiale qui contient 23 monuments célèbres trouvés dans le monde entier. Un avion vole du coin supérieur gauche. Un train circule du coin inférieur droit. Les monuments sont situés à différents endroits de cette scène, notamment le célèbre Sphinx en Égypte, la Tour Eiffel en France, la Tour de Tokyo au Japon et le marché flottant au Vietnam. Votre personnage pour explorer cette scène est un robot.", + "Landmarks.leaningTowerPisa": "La tour penchée de Pise", + "Landmarks.machuPicchu": "Matchu Picchu", + "Landmarks.niagaraFalls": "Chutes du Niagara", + "Landmarks.operaHouse": "L'Opéra de Sydney", + "Landmarks.plane": "un avion", + "Landmarks.stBasils": "Cathédrale Saint-Basile", + "Landmarks.statueLiberty": "la statue de la Liberté", + "Landmarks.stonehenge": "Stonehenge", + "Landmarks.tableMountain": "Montagne de la Table", + "Landmarks.tajMahal": "le palais Taj Mahal", + "Landmarks.tokyoTower": "la Tour de Tokyo", + "Landmarks.train": "un train", + "Landmarks.windmill": "les moulins à vents des Pais-Bas", + "Marble.bricks": "briques", + "Marble.character": "la bille", + "Marble.label": "Un labyrinthe fait de briques de différentes couleurs. Ton personnage dans cette scène est une bille. Il y a un sentier à travers le labyrinthe qui commence là où se trouve votre bille et il y a plusieurs façons d'échapper au labyrinthe.", + "MusicBand.character": "la note de musique", + "MusicBand.drumKit": "la batterie", + "MusicBand.guitar": "la guitare", + "MusicBand.label": "Une scène de groupe de musique avec des instruments de musique. Les instruments sont disposés sur 2 rangées, une rangée au milieu de la scène et une en bas de la scène. Dans la rangée du haut, il y a une guitare, une batterie et un saxophone. Dans la rangée du bas, il y a un tambourin, un xylophone avec un maillet, un microphone sur pied et un synthétiseur. Au sommet de la scène, des projecteurs éclairent les instruments. Et dans les coins inférieurs gauche et droit se trouvent des haut-parleurs. Votre personnage dans cette scène est une note de musique.", + "MusicBand.loudspeaker": "un haut-parleur", + "MusicBand.microphone": "un mircrophone sur un pied", + "MusicBand.saxophone": "le saxophone", + "MusicBand.spotlight": "un projeteur", + "MusicBand.synthesizer": "une synthésiseur", + "MusicBand.tambourine": "une tambourine", + "MusicBand.xylophone": "un xylophone avec un maillet", + "PenDownToggleSwitch.penDown": "Stylo vers le bas", + "PlayButton.pause": "pause", + "PlayButton.play": "lire", + "PlayControls.heading": "Commandes de lire", + "ProgramBlockEditor.beginningBlock": "Ajouter l'action sélectionnée {actionName} au début du programme", + "ProgramBlockEditor.betweenBlocks": "Ajouter l'action sélectionnée {actionName} entre la position {previousStepNumber}, {previousStepActionName} et la position {nextStepNumber}, {nextStepActionName}", + "ProgramBlockEditor.blocks.noCommandSelected": "Assurez-vous qu'une action est sélectionnée", + "ProgramBlockEditor.command": "{blockName}, position {index} du programme actuel", + "ProgramBlockEditor.lastBlock": "Ajouter l'action sélectionnée {actionName} à la fin du programme", + "ProgramBlockEditor.nestedCommand": "{blockName}, position {index} de la boucle {parentLoopLabel}", + "ProgramBlockEditor.program.deleteAll": "Supprimez toutes les étapes de votre programme", + "ProgramBlockEditor.toggleAddNodeExpandMode": "afficher les boutons « ajouter »", + "ProgramSequence.heading": "Séquence du programme", + "ProgramSpeedController.slider": "Vitesse de lecture du programme", + "RefreshButton": "Rafraîchir", + "Savannah.alligator": "L'alligator", + "Savannah.babyAlligator": "le bébé alligator", + "Savannah.babyGiraffe": "le bébé giraffe", + "Savannah.bush": "une buisson", + "Savannah.character": "la jeep", + "Savannah.flamingo": "le flamant", + "Savannah.giraffe": "le giraffe", + "Savannah.hippo": "l'hippopotame", + "Savannah.label": "Une scène de savane. Un lion rugit au sommet d’une falaise au-dessus de l’horizon. Une mère et son bébé girafe parcourent la savane. Deux crocodiles, un flamant rose et un hippopotame boivent l'eau d'un étang entouré d'arbres. Ton personnage dans cette scène est un Jeep.", + "Savannah.lion": "le lion", + "Savannah.pond": "l'étang", + "Savannah.tree": "un arbre", + "Scene.description": "Scène, dans {backgroundName}, une grille {numColumns} par {numRows}. {characterDescription}", + "Scene.heading": "scène", + "SceneMessage.close": "fermer le message", + "Sketchpad.character": "le robot", + "Sketchpad.label": "Un carnet de croquis vierge avec des lignes de quadrillage. Ton personnage dans cette scène est un robot.", + "Space.aliens": "les extraterrestres", + "Space.asteroid": "un astéroïde", + "Space.character": "le vaisseau spatial", + "Space.earth": "la Terre", + "Space.label": "Une scène spatiale avec la Terre, Mars, Saturne et la Lune réparties dans l'espace. Entre ces planètes se trouvent des roches spatiales, des météores, un satellite et deux extraterrestres. Votre personnage dans cette scène est un vaisseau spatial.", + "Space.mars": "Mars", + "Space.meteor": "un météore", + "Space.moon": "La Lune", + "Space.satellite": "le satellite", + "Space.saturn": "Saturne", + "Space.star": "une étoile", + "Sports.badmintonShuttlecock": "volant de badminton", + "Sports.baseballGloveAndBall": "gant et balle de baseball", + "Sports.basketball": "basketball", + "Sports.bicycle": "bicyclette", + "Sports.bowlingBallAndPins": "boule de quilles et quilles", + "Sports.boxingGloves": "gants de boxe", + "Sports.character": "la voiturette de golf", + "Sports.cricketBatAndBall": "batte et balle de cricket", + "Sports.curlingStone": "pierre à friser", + "Sports.fieldHockeyStickAndBall": "bâton et balle de hockey sur gazon", + "Sports.football": "football", + "Sports.golfBall": "balle de golf", + "Sports.hockeyStickAndPuck": "bâton de hockey et rondelle", + "Sports.iceSkates": "patins à glace", + "Sports.label": "Une scène sportive avec 23 équipements sportifs différents répartis sur la scène. Votre personnage est une voiturette de golf.", + "Sports.martialArtsUniform": "uniforme d'arts martiaux", + "Sports.rowingBoat": "bateau à rames", + "Sports.runningShoes": "chaussures de course", + "Sports.singlet": "chandail", + "Sports.skisAndSkiPoles": "skis et bâtons de ski", + "Sports.soccerBall": "ballon de soccer", + "Sports.swimmingGoggles": "lunettes de natation", + "Sports.tableTennisRacket": "raquette de ping-pong", + "Sports.tennisRacketAndBall": "raquette et balle de tennis", + "Sports.volleyballBall": "ballon de volleyball", + "StopButton": "Arrêt", + "ThemeSelector.iconButton": "Sélecteur de thème", + "TileDescription.black": "noire", + "TileDescription.brown": "brun", + "TileDescription.darkBlue": "bleu foncé", + "TileDescription.gem": "gemme", + "TileDescription.gold": "or", + "TileDescription.green": "vert", + "TileDescription.grey": "gris", + "TileDescription.lightBlue": "bleu pâle", + "TileDescription.none": "gomme d'arrière-plan personnalisée", + "TileDescription.orange": "orange", + "TileDescription.pink": "rose", + "TileDescription.purple": "pourpre", + "TileDescription.red": "rouge", + "TileDescription.treats": "friandises", + "TileDescription.wall": "mur", + "TileDescription.white": "blanc", + "TileDescription.yellow": "jaune", + "TilePanel.heading": "Conception d'arrière-plan personnalisée", + "UI.ActionsMenu.title": "Actions", + "UI.ActionsMenuItem.command.backward1": "Reculer d'un pas", + "UI.ActionsMenuItem.command.forward1": "Avancer d'un pas", + "UI.ActionsMenuItem.command.left45": "Tourner à gauche à 45 degrés", + "UI.ActionsMenuItem.command.left90": "Tourner à gauche à 90 degrés", + "UI.ActionsMenuItem.command.loop": "Boucle", + "UI.ActionsMenuItem.command.right45": "Tourner à droite à 45 degrées", + "UI.ActionsMenuItem.command.right90": "Tourner à droite à 90 degrées", + "UI.ActionsMenuItem.usedItemToggleLabel": "(utilisé)", + "UI.ActionsSimplificationModal.title": "Actions disponibles", + "UI.AmusementPark.name": "Parc d'attractions", + "UI.App.privacyModalToggle": "Confidentialité", + "UI.AtlanticCanada.name": "Canada atlantique", + "UI.Camping.name": "Voyage de camping", + "UI.Cancel": "Annuler", + "UI.CharacterMessageBuilder.endOfScene": "Votre personnage a atteint la fin de la scène", + "UI.CharacterMessageBuilder.hitWall": "Votre personnage a frappé un mur sur {columnLabel}{rowLabel}", + "UI.Close": "Fermer", + "UI.CommandPalette.controlsTitle": "Contrôles", + "UI.CommandPalette.movementsTitle": "Mouvements", + "UI.ConfirmDeleteAllModal.confirmButton": "Supprimer", + "UI.ConfirmDeleteAllModal.content": "Êtes-vous certain de vouloir supprimer toutes les étapes de votre programme ?", + "UI.ConfirmDeleteAllModal.title": "Supprimer le programme", + "UI.DeepOcean.name": "Océan profond", + "UI.EuropeTrip.name": "Voyage en Europe", + "UI.GroceryStore.name": "Épicerie", + "UI.Haunted.name": "Maison hantée", + "UI.KeyboardInputModal.Description.addCommandToBeginning": "Appuyez sur {keyboardShortcut} pour ajouter la commande sélectionnée au début du programme.", + "UI.KeyboardInputModal.Description.addCommandToEnd": "Appuyez sur {keyboardShortcut} pour ajouter la commande sélectionnée à la fin du programme.", + "UI.KeyboardInputModal.Description.announceScene": "Appuyez sur {keyboardShortcut} pour annoncer la position et l'orientation du caractère.", + "UI.KeyboardInputModal.Description.decreaseProgramSpeed": "Appuyez sur {keyboardShortcut} pour ralentir la lecture du programme.", + "UI.KeyboardInputModal.Description.deleteCurrentStep": "Appuyez sur {keyboardShortcut} pour supprimer l'étape actuellement ciblée.", + "UI.KeyboardInputModal.Description.increaseProgramSpeed": "Appuyez sur {keyboardShortcut} pour accélérer la lecture du programme.", + "UI.KeyboardInputModal.Description.playPauseProgram": "Appuyez sur {keyboardShortcut} pour lire ou mettre en pause le programme.", + "UI.KeyboardInputModal.Description.refreshScene": "Appuyez sur {keyboardShortcut} pour rafraîchir la scène.", + "UI.KeyboardInputModal.Description.showHide": "Appuyez sur {keyboardShortcut} pour afficher le menu des raccourcis clavier.", + "UI.KeyboardInputModal.Description.stopProgram": "Appuyez sur {keyboardShortcut} pour arrêter le programme.", + "UI.KeyboardInputModal.Description.toggleAnnouncements": "Appuyez sur {keyboardShortcut} pour basculer entre les annonces.", + "UI.KeyboardInputModal.KeyIcons.A": "A", + "UI.KeyboardInputModal.KeyIcons.Alt": "alt", + "UI.KeyboardInputModal.KeyIcons.B": "B", + "UI.KeyboardInputModal.KeyIcons.Control": "ctrl", + "UI.KeyboardInputModal.KeyIcons.D": "D", + "UI.KeyboardInputModal.KeyIcons.E": "E", + "UI.KeyboardInputModal.KeyIcons.GreaterThan": ">", + "UI.KeyboardInputModal.KeyIcons.I": "I", + "UI.KeyboardInputModal.KeyIcons.LessThan": "<", + "UI.KeyboardInputModal.KeyIcons.P": "P", + "UI.KeyboardInputModal.KeyIcons.QuestionMark": "?", + "UI.KeyboardInputModal.KeyIcons.R": "R", + "UI.KeyboardInputModal.KeyIcons.S": "S", + "UI.KeyboardInputModal.KeyIcons.Shift": "déclage", + "UI.KeyboardInputModal.KeyLabels.A": "a", + "UI.KeyboardInputModal.KeyLabels.Alt": "alt", + "UI.KeyboardInputModal.KeyLabels.B": "b", + "UI.KeyboardInputModal.KeyLabels.Control": "contrôle", + "UI.KeyboardInputModal.KeyLabels.D": "d", + "UI.KeyboardInputModal.KeyLabels.E": "e", + "UI.KeyboardInputModal.KeyLabels.GreaterThan": "plus grand que", + "UI.KeyboardInputModal.KeyLabels.I": "i", + "UI.KeyboardInputModal.KeyLabels.LessThan": "plus petit que", + "UI.KeyboardInputModal.KeyLabels.P": "p", + "UI.KeyboardInputModal.KeyLabels.QuestionMark": "point d'interrogation", + "UI.KeyboardInputModal.KeyLabels.R": "r", + "UI.KeyboardInputModal.KeyLabels.S": "s", + "UI.KeyboardInputModal.Scheme.Descriptions.alt": "alt (Apple: option)", + "UI.KeyboardInputModal.Scheme.Descriptions.controlalt": "contrôle+alt (Apple: contrôle+option)", + "UI.KeyboardInputModal.Title": "raccourcis clavier", + "UI.Landmarks.name": "Points de repère", + "UI.Marble.name": "Course de billes", + "UI.MusicBand.name": "Groupe de musique", + "UI.Off": "étient", + "UI.On": "allumé", + "UI.PrivacyModal.contactUs": "Contactez-nous", + "UI.PrivacyModal.section010.Heading": "Mis à jour le 4 janvier 2024", + "UI.PrivacyModal.section010.block010": "Chez Weavly, nous croyons que la vie privée est un droit humain fondamental et reconnaissons son importance pour notre communauté, en particulier en ce qui concerne les enfants et les parents.", + "UI.PrivacyModal.section010.block020": "Cette page explique :", + "UI.PrivacyModal.section010.block030.item010": "Quel type d'informations nous stockons sur notre site Web (http://create.weavly.org/)", + "UI.PrivacyModal.section010.block030.item020": "Comment ces renseignements sont utilisés et traités", + "UI.PrivacyModal.section010.block030.item030": "Comment nous protégeons vos renseignements", + "UI.PrivacyModal.section020.Heading": "Quelles informations Weavly stocke-t-il ?", + "UI.PrivacyModal.section020.block010": "Lorsque vous utilisez Weavly, nous pouvons stocker des informations sur la façon dont vous accédez et utilisez Weavly. Nous stockons les informations suivantes indépendamment sur chaque paire navigateur/appareil sur lequel vous utilisez Weavly :", + "UI.PrivacyModal.section020.block020.item010": "Vos réglages préférés pour le thème de couleur d'affichage et les raccourcis clavier", + "UI.PrivacyModal.section020.block020.item020": "Your visible set of action blocks on the action panel", + "UI.PrivacyModal.section020.block020.item030": "Votre arrière-plan sélectionné pour la scène", + "UI.PrivacyModal.section020.block020.item040": "Votre programme créé", + "UI.PrivacyModal.section020.block020.item050": "Toute ligne tracée sur la scène suite à l'exécution de votre programme", + "UI.PrivacyModal.section020.block020.item060": "La dernière position de ton personnage sur la scène", + "UI.PrivacyModal.section020.block020.item070": "La position de départ de votre personnage", + "UI.PrivacyModal.section020.block020.item080": "La version de Weavly utilisée", + "UI.PrivacyModal.section020.block030": "Vous pouvez supprimer toutes les informations générées par Weavly à partir de votre utilisation en effaçant le cache et le stockage local de votre navigateur. Consultez la documentation de votre navigateur pour plus de détails.", + "UI.PrivacyModal.section030.Heading": "Comment Weavly utilise-t-il ces renseignements ?", + "UI.PrivacyModal.section030.block010": "Les informations générées sont conservées dans un stockage local sur votre appareil pour rendre votre utilisation de Weavly plus pratique :", + "UI.PrivacyModal.section030.block020.item010": "Vos paramètres pour l'environnement de codage sont stockés afin que vous n'ayez pas à les ajuster à chaque fois que vous lancez Weavly", + "UI.PrivacyModal.section030.block020.item020": "Si vous fermez accidentellement ou intentionnellement votre navigateur, l'environnement de codage sera le même que lorsque vous l'avez quitté la prochaine fois que vous lancerez Weavly", + "UI.PrivacyModal.section030.block030": "Bien que le stockage de vos informations dans le navigateur soit pratique chaque fois que vous accédez à Weavly, cela peut entraîner des problèmes sur les ordinateurs partagés. Par conséquent, quelqu'un qui utilise l'ordinateur après vous pourra peut-être accéder à vos paramètres et à votre programme Weavly.", + "UI.PrivacyModal.section040.Heading": "Comment protégeons-nous vos renseignements ?", + "UI.PrivacyModal.section040.block010": "La sécurité de vos données est importante pour nous, mais n'oubliez pas qu'aucune méthode de stockage électronique n'est sécurisée à 100 %. Nous utilisons actuellement le stockage local du navigateur pour stocker les données spécifiées. Les informations sont stockées dans le navigateur par domaine (create.weavly.org) et seul le code exécuté à partir de ce domaine peut y accéder. Ainsi, d’autres sites Web ne peuvent pas accéder aux données. Cependant, le stockage local n'est pas chiffré sur le disque et toute personne ayant accès à l'appareil peut accéder aux données.", + "UI.PrivacyModal.section050.Heading": "Confidentialité des enfants", + "UI.PrivacyModal.section050.block010": "Nous ne recueillons pas sciemment de renseignements personnels identifiables auprès d'une personne de moins de 18 ans. on peut prendre des mesures pour supprimer ces informations. S'il vous plaît {contactLink}", + "UI.PrivacyModal.section060.Heading": "Modifications à cette politique de confidentialité", + "UI.PrivacyModal.section060.block010": "Nous pouvons mettre à jour notre politique de confidentialité de temps à autre. Nous vous aviserons de tout changement en publiant la nouvelle politique de confidentialité sur cette page. Nous vous aviserons de tout changement entrant en vigueur en mettant à jour la « date d’entrée en vigueur » en haut de la présente politique de confidentialité. Il vous est recommandé de consulter périodiquement cette politique de confidentialité pour tout changement. Les modifications apportées à cette politique de confidentialité entrent en vigueur dès leur publication sur cette page.", + "UI.PrivacyModal.section070.Heading": "Contactez-nous", + "UI.PrivacyModal.section070.block010": "Si vous avez des questions sur cette politique de confidentialité, veuillez {contactLink}.", + "UI.PrivacyModal.title": "Politique de confidentialité de Weavly ", + "UI.ProgramBlockEditor.programHeading": "programme", + "UI.Savannah.name": "Savane", + "UI.Save": "Enregistrer", + "UI.ShareButton": "Partager", + "UI.ShareModal.copy": "Copier le lien", + "UI.ShareModal.description1": "Un lien vers le programme que vous avez créé a été copié dans le presse-papiers.", + "UI.ShareModal.description2": "Vous pouvez aussi copier le lien ci-dessous pour le partager avec qui vous voulez.", + "UI.ShareModal.title": "Partager le lien", + "UI.Sketchpad.name": "Carnet de croquis", + "UI.SoundOptionsModal.allSounds": "Tous les sons", + "UI.SoundOptionsModal.announcements": "Annonces audio", + "UI.SoundOptionsModal.musicalSounds": "Sons musicaux", + "UI.SoundOptionsModal.title": "Options sonores", + "UI.Space.name": "Espace", + "UI.Sports.name": "Sports", + "UI.ThemeSelector.option.contrast": "Contraste élevé", + "UI.ThemeSelector.option.dark": "Sombre", + "UI.ThemeSelector.option.default": "Défaut", + "UI.ThemeSelector.option.gray": "Niveaux de gris", + "UI.ThemeSelector.option.light": "Clair", + "UI.ThemeSelector.title": "Thèmes", + "UI.WorldSelector.Prompt": "Choisissez un arrière-plan pour votre scène.", + "UI.WorldSelector.Title": "Arrière-plan de scène", + "WorldSelectorButton.heading": "Sélecteur d'arrière-plan de scène", + "WorldSelectorButton.label": "Sélecteur d'arrière-plan de scène" } -} +} \ No newline at end of file diff --git a/tools/check_messages.js b/tools/check_messages.js new file mode 100644 index 00000000..cc351461 --- /dev/null +++ b/tools/check_messages.js @@ -0,0 +1,30 @@ +const messagesUtils = require('./messagesUtils.js'); + +function checkMessages(messages) { + const langCodes = messagesUtils.getLangCodes(messages); + const messageKeys = messagesUtils.getAllMessageKeys(messages, langCodes); + let ok = true; + for (const lang of langCodes) { + for (const key of messageKeys) { + if (!Object.hasOwn(messages[lang], key)) { + process.stderr.write(`Missing: ${lang}.${key}\n`); + ok = false; + } + } + } + return ok; +} + +if (process.argv.length !== 3) { + process.stderr.write("usage: check_messages.js file\n"); + process.exit(2); +} + +const filename = process.argv[2]; + +const messages = messagesUtils.loadMessages(filename); + +const ok = checkMessages(messages); +if (!ok) { + process.exit(1); +} diff --git a/tools/messages2tsv.js b/tools/messages2tsv.js new file mode 100644 index 00000000..e7211730 --- /dev/null +++ b/tools/messages2tsv.js @@ -0,0 +1,39 @@ +const fs = require('fs'); +const messagesUtils = require('./messagesUtils.js'); + +function writeTSV(messages, stream) { + const langCodes = messagesUtils.getLangCodes(messages); + const messageKeys = messagesUtils.getAllMessageKeys(messages, langCodes); + // Header + stream.write("keys"); + for (const lang of langCodes) { + stream.write(`\t${lang}`); + } + stream.write("\n"); + // Messages + for (const key of messageKeys) { + stream.write(key); + for (const lang of langCodes) { + stream.write("\t"); + if (Object.hasOwn(messages[lang], key)) { + stream.write(messages[lang][key]); + } else { + stream.write("MISSING"); + } + } + stream.write("\n"); + } +} + +if (process.argv.length !== 4) { + process.stderr.write("usage: messages2tsv.js output-file input-file\n"); + process.exit(2); +} + +const outputFilename = process.argv[2]; +const inputFilename = process.argv[3]; + +const messages = messagesUtils.loadMessages(inputFilename); +const outStream = fs.createWriteStream(outputFilename, {encoding: 'utf8'}); + +writeTSV(messages, outStream); diff --git a/tools/messagesUtils.js b/tools/messagesUtils.js new file mode 100644 index 00000000..d4ced3c4 --- /dev/null +++ b/tools/messagesUtils.js @@ -0,0 +1,19 @@ +const fs = require('fs'); + +exports.loadMessages = function(filename) { + return JSON.parse(fs.readFileSync(filename, {encoding: 'utf8'})); +}; + +exports.getLangCodes = function(messages) { + return Object.keys(messages).sort(); +}; + +exports.getAllMessageKeys = function(messages, langCodes) { + const messageKeys = new Set(); + for (const lang of langCodes) { + for (const key of Object.keys(messages[lang])) { + messageKeys.add(key); + } + } + return Array.from(messageKeys).sort(); +}; diff --git a/tools/tsv2messages.js b/tools/tsv2messages.js new file mode 100644 index 00000000..408d91ae --- /dev/null +++ b/tools/tsv2messages.js @@ -0,0 +1,49 @@ +const fs = require('fs'); +const readline = require('readline'); + +function readMessagesFromTSV(stream, callback) { + let langCodes = []; + const messages = {}; + + const rl = readline.createInterface({ + input: stream, + crlfDelay: Infinity + }); + + let firstLine = true; + rl.on('line', (line) => { + const cols = line.split("\t"); + if (firstLine) { + langCodes = cols.slice(1); + for (const lang of langCodes) { + messages[lang] = {}; + } + firstLine = false; + } else { + if (cols.length > 0) { + const key = cols[0]; + for (let i = 0; i < langCodes.length; i++) { + messages[langCodes[i]][key] = cols[i+1]; + } + } + } + }); + + rl.on('close', () => { + callback(messages); + }); +} + +if (process.argv.length !== 4) { + process.stderr.write("usage: tsv2messages.js output-file input-file\n"); + process.exit(2); +} + +const outputFilename = process.argv[2]; +const inputFilename = process.argv[3]; + +const inStream = fs.createReadStream(inputFilename, {encoding: 'utf8'}); + +readMessagesFromTSV(inStream, (messages) => { + fs.writeFileSync(outputFilename, JSON.stringify(messages, null, 4), {encoding: 'utf8'}); +});