diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index ea67c3c1..45bcbfe0 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -10,7 +10,6 @@ on: pull_request: jobs: - continuous-integration-front: name: "Continuous Integration Front" @@ -46,6 +45,10 @@ jobs: run: ./templater.sh working-directory: "front" + - name: "Generate i18n files" + run: yarn run typesafe-i18n + working-directory: "front" + - name: "Build" run: yarn run build env: diff --git a/.github/workflows/push-to-npm.yml b/.github/workflows/push-to-npm.yml index 571a16e6..750ef224 100644 --- a/.github/workflows/push-to-npm.yml +++ b/.github/workflows/push-to-npm.yml @@ -43,6 +43,10 @@ jobs: run: ./templater.sh working-directory: "front" + - name: "Generate i18n files" + run: yarn run typesafe-i18n + working-directory: "front" + - name: "Build" run: yarn run build-typings env: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b3361333..2d3f5d0b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,13 +1,13 @@ # Contributing to WorkAdventure -Are you looking to help on WorkAdventure? Awesome, feel welcome and read the following sections in order to know how to +Are you looking to help on WorkAdventure? Awesome, feel welcome and read the following sections in order to know how to ask questions and how to work on something. ## Contributions we are seeking We love to receive contributions from our community — you! -There are many ways to contribute, from writing tutorials or blog posts, improving the documentation, +There are many ways to contribute, from writing tutorials or blog posts, improving the documentation, submitting bug reports and feature requests or writing code which can be incorporated into WorkAdventure itself. ## Contributing external resources @@ -16,7 +16,7 @@ You can share your work on maps / articles / videos related to WorkAdventure on ## Developer documentation -Documentation targeted at developers can be found in the [`/docs/dev`](docs/dev/) +Documentation targeted at developers can be found in the [`/docs/dev`](docs/dev/) ## Using the issue tracker @@ -34,11 +34,11 @@ Finally, you can come and talk to the WorkAdventure core team... on WorkAdventur ## Pull requests -Good pull requests - patches, improvements, new features - are a fantastic help. They should remain focused in scope +Good pull requests - patches, improvements, new features - are a fantastic help. They should remain focused in scope and avoid containing unrelated commits. -Please ask first before embarking on any significant pull request (e.g. implementing features, refactoring code), -otherwise you risk spending a lot of time working on something that the project's developers might not want to merge +Please ask first before embarking on any significant pull request (e.g. implementing features, refactoring code), +otherwise you risk spending a lot of time working on something that the project's developers might not want to merge into the project. You can ask us on [Discord](https://discord.gg/YGtngdh9gt) or in the [GitHub issues](https://github.com/thecodingmachine/workadventure/issues). @@ -54,7 +54,7 @@ $ yarn install $ yarn run prepare ``` -If you don't have the precommit hook installed (or if you committed code before installing the precommit hook), you will need +If you don't have the precommit hook installed (or if you committed code before installing the precommit hook), you will need to run code linting manually: ```console @@ -72,7 +72,7 @@ Nevertheless, if your code can be unit tested, please provide a unit test (we us If you are providing a new feature, you should setup a test map in the `maps/tests` directory. The test map should contain some description text describing how to test the feature. -* if the features is meant to be manually tested, you should modify the `maps/tests/index.html` file to add a reference +* if the features is meant to be manually tested, you should modify the `maps/tests/index.html` file to add a reference to your newly created test map * if the features can be automatically tested, please provide a testcafe test @@ -90,8 +90,8 @@ $ npm run test ``` Note: If your tests fail on a Javascript error in "sockjs", this is due to the -Webpack live reload. The Webpack live reload feature is conflicting with testcafe. This is why we recommend starting -WorkAdventure with the `LIVE_RELOAD=0` environment variable. +Webpack live reload. The Webpack live reload feature is conflicting with testcafe. This is why we recommend starting +WorkAdventure with the `LIVE_RELOAD=0` environment variable. End-to-end tests can take a while to run. To run only one test, use: @@ -107,3 +107,7 @@ $ LIVE_RELOAD=0 docker-compose up -d # Wait 2-3 minutes for the environment to start, then: $ PROJECT_DIR=$(pwd) docker-compose -f docker-compose.testcafe.yml up ``` + +### A bad wording or a missing language + +If you notice a translation error or missing language you can help us by following the [how to translate](docs/dev/how-to-translate.md) documentation. diff --git a/back/yarn.lock b/back/yarn.lock index d22087d2..04c6929b 100644 --- a/back/yarn.lock +++ b/back/yarn.lock @@ -1504,9 +1504,9 @@ natural-compare@^1.4.0: integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= node-fetch@^2.6.5: - version "2.6.6" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" - integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== dependencies: whatwg-url "^5.0.0" diff --git a/docs/dev/how-to-translate.md b/docs/dev/how-to-translate.md new file mode 100644 index 00000000..b72b045a --- /dev/null +++ b/docs/dev/how-to-translate.md @@ -0,0 +1,76 @@ +# How to translate WorkAdventure + +We use the [typesafe-i18n](https://github.com/ivanhofer/typesafe-i18n) package to handle the translation. + +## Add a new language + +It is very easy to add a new language! + +First, in the `front/src/i18n` folder create a new folder with the language code as name (the language code according to [RFC 5646](https://datatracker.ietf.org/doc/html/rfc5646)). + +In the previously created folder, add a file named index.ts with the following content containing your language information (french from France in this example): + +```ts +import type { Translation } from "../i18n-types"; + +const fr_FR: Translation = { + ...en_US, + language: "Français", + country: "France", +}; + +export default fr_FR; +``` + +## Add a new key + +### Add a simple key + +The keys are searched by a path through the properties of the sub-objects and it is therefore advisable to write your translation as a JavaScript object. + +Please use kamelcase to name your keys! + +Example: + +```ts +{ + messages: { + coffeMachine: { + start: "Coffe machine has been started!"; + } + } +} +``` + +In the code you can translate using `$LL`: + +```ts +import LL from "../../i18n/i18n-svelte"; + +console.log($LL.messages.coffeMachine.start()); +``` + +### Add a key with parameters + +You can also use parameters to make the translation dynamic. +Use the tag { [parameter name] } to apply your parameters in the translations + +Example: + +```ts +{ + messages: { + coffeMachine: { + playerStart: "{ playerName } started the coffee machine!"; + } + } +} +``` + +In the code you can use it like this: + +```ts +$LL.messages.coffeMachine.playerStart.start({ + playerName: "John", +}); +``` diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index d9a89bd1..c3aa808a 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -36,6 +36,23 @@ WA.onInit().then(() => { }) ``` +### Get the player language + +``` +WA.player.language: string; +``` + +The current language of player is available from the `WA.player.language` property. + +{.alert.alert-info} +You need to wait for the end of the initialization before accessing `WA.player.language` + +```typescript +WA.onInit().then(() => { + console.log('Player language: ', WA.player.language); +}) +``` + ### Get the tags of the player ``` @@ -173,6 +190,37 @@ Example: WA.player.state.toto //will retrieve the variable ``` +### Move player to position +```typescript +WA.player.moveTo(x: number, y: number, speed?: number): Promise<{ x: number, y: number, cancelled: boolean }>; +``` +Player will try to find shortest path to the destination point and proceed to move there. +```typescript +// Let's move player to x: 250 y: 250 with speed of 10 +WA.player.moveTo(250, 250, 10); +``` +You can also chain movement like this: +```typescript +// Player will move to the next point after reaching first one +await WA.player.moveTo(250, 250, 10); +await WA.player.moveTo(500, 0, 10); +``` +Or like this: +```typescript +// Player will move to the next point after reaching first one or stop if the movement was cancelled +WA.player.moveTo(250, 250, 10).then((result) => { + if (!result.cancelled) { + WA.player.moveTo(500, 0, 10); + } +}); +``` +It is possible to get the information about current player's position on stop and if the movement was interrupted +```typescript +// Result will store x and y of Player at the moment of movement's end and information if the movement was interrupted +const result = await WA.player.moveTo(250, 250, 10); +// result: { x: number, y: number, cancelled: boolean } +``` + ### Set the outline color of the player ``` WA.player.setOutlineColor(red: number, green: number, blue: number): Promise; diff --git a/docs/maps/entry-exit.md b/docs/maps/entry-exit.md index 6f98af93..2040beb3 100644 --- a/docs/maps/entry-exit.md +++ b/docs/maps/entry-exit.md @@ -65,3 +65,24 @@ How to use entry point : * To enter via this entry point, simply add a hash with the entry point name to the URL ("#[_entryPointName_]"). For instance: "`https://workadventu.re/_/global/mymap.com/path/map.json#my-entry-point`". * You can of course use the "#" notation in an exit scene URL (so an exit scene URL will point to a given entry scene URL) + +## Defining destination point with moveTo parameter + +We are able to direct a Woka to the desired place immediately after spawn. To make users spawn on an entry point and then, walk automatically to a meeting room, simply add `moveTo` as an additional parameter of URL: + +*Use default entry point* +``` +.../my_map.json#&moveTo=exit +``` +*Define entry point and moveTo parameter like this...* +``` +.../my_map.json#start&moveTo=meeting-room +``` +*...or like this* +``` +.../my_map.json#moveTo=meeting-room&start +``` + +For this to work, moveTo must be equal to the layer name of interest. This layer should have at least one tile defined. In case of layer having many tiles, user will go to one of them, randomly selected. + +![](images/moveTo-layer-example.png) \ No newline at end of file diff --git a/docs/maps/images/moveTo-layer-example.png b/docs/maps/images/moveTo-layer-example.png new file mode 100644 index 00000000..12e8a4ad Binary files /dev/null and b/docs/maps/images/moveTo-layer-example.png differ diff --git a/front/.eslintrc.js b/front/.eslintrc.js index ed94b3b2..fa57ebf4 100644 --- a/front/.eslintrc.js +++ b/front/.eslintrc.js @@ -8,7 +8,7 @@ module.exports = { "extends": [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking" + "plugin:@typescript-eslint/recommended-requiring-type-checking", ], "globals": { "Atomics": "readonly", @@ -23,7 +23,7 @@ module.exports = { }, "plugins": [ "@typescript-eslint", - "svelte3" + "svelte3", ], "overrides": [ { @@ -33,6 +33,7 @@ module.exports = { ], "rules": { "no-unused-vars": "off", + "eol-last": ["error", "always"], "@typescript-eslint/no-explicit-any": "error", "no-throw-literal": "error", // TODO: remove those ignored rules and write a stronger code! diff --git a/front/.prettierignore b/front/.prettierignore index 26de759f..8d8c68de 100644 --- a/front/.prettierignore +++ b/front/.prettierignore @@ -1,2 +1,5 @@ src/Messages/generated src/Messages/JsonMessages +src/i18n/i18n-svelte.ts +src/i18n/i18n-types.ts +src/i18n/i18n-util.ts diff --git a/front/.typesafe-i18n.json b/front/.typesafe-i18n.json new file mode 100644 index 00000000..0cecbe32 --- /dev/null +++ b/front/.typesafe-i18n.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://unpkg.com/typesafe-i18n@2.59.0/schema/typesafe-i18n.json", + "baseLocale": "en-US", + "adapter": "svelte" +} \ No newline at end of file diff --git a/front/dist/resources/translations/.gitignore b/front/dist/resources/translations/.gitignore new file mode 100644 index 00000000..a6c57f5f --- /dev/null +++ b/front/dist/resources/translations/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/front/package.json b/front/package.json index 4a4e78f6..564e1485 100644 --- a/front/package.json +++ b/front/package.json @@ -64,10 +64,12 @@ "socket.io-client": "^2.3.0", "standardized-audio-context": "^25.2.4", "ts-proto": "^1.96.0", - "uuidv4": "^6.2.10" + "typesafe-i18n": "^2.59.0", + "uuidv4": "^6.2.10", + "zod": "^3.11.6" }, "scripts": { - "start": "run-p templater serve svelte-check-watch", + "start": "run-p templater serve svelte-check-watch typesafe-i18n", "templater": "cross-env ./templater.sh", "serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open", "build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack", @@ -79,7 +81,8 @@ "svelte-check-watch": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\" --watch", "svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\"", "pretty": "yarn prettier --write 'src/**/*.{ts,svelte}'", - "pretty-check": "yarn prettier --check 'src/**/*.{ts,svelte}'" + "pretty-check": "yarn prettier --check 'src/**/*.{ts,svelte}'", + "typesafe-i18n": "typesafe-i18n --no-watch" }, "lint-staged": { "*.svelte": [ diff --git a/front/src/Api/Events/GameStateEvent.ts b/front/src/Api/Events/GameStateEvent.ts index 6d20ac9e..80c07e5a 100644 --- a/front/src/Api/Events/GameStateEvent.ts +++ b/front/src/Api/Events/GameStateEvent.ts @@ -5,6 +5,7 @@ export const isGameStateEvent = new tg.IsInterface() roomId: tg.isString, mapUrl: tg.isString, nickname: tg.isString, + language: tg.isUnion(tg.isString, tg.isUndefined), uuid: tg.isUnion(tg.isString, tg.isUndefined), startLayerName: tg.isUnion(tg.isString, tg.isNull), tags: tg.isArray(tg.isString), diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 93d0735c..e56699a7 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -34,6 +34,8 @@ import type { ChangeZoneEvent } from "./ChangeZoneEvent"; import type { CameraSetEvent } from "./CameraSetEvent"; import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent"; import { isColorEvent } from "./ColorEvent"; +import { isMovePlayerToEventConfig } from "./MovePlayerToEvent"; +import { isMovePlayerToEventAnswer } from "./MovePlayerToEventAnswer"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -173,6 +175,10 @@ export const iframeQueryMapTypeGuards = { query: tg.isUndefined, answer: isPlayerPosition, }, + movePlayerTo: { + query: isMovePlayerToEventConfig, + answer: isMovePlayerToEventAnswer, + }, }; type GuardedType = T extends (x: unknown) => x is infer T ? T : never; diff --git a/front/src/Api/Events/MovePlayerToEvent.ts b/front/src/Api/Events/MovePlayerToEvent.ts new file mode 100644 index 00000000..462e2f43 --- /dev/null +++ b/front/src/Api/Events/MovePlayerToEvent.ts @@ -0,0 +1,11 @@ +import * as tg from "generic-type-guard"; + +export const isMovePlayerToEventConfig = new tg.IsInterface() + .withProperties({ + x: tg.isNumber, + y: tg.isNumber, + speed: tg.isOptional(tg.isNumber), + }) + .get(); + +export type MovePlayerToEvent = tg.GuardedType; diff --git a/front/src/Api/Events/MovePlayerToEventAnswer.ts b/front/src/Api/Events/MovePlayerToEventAnswer.ts new file mode 100644 index 00000000..67d2f9ae --- /dev/null +++ b/front/src/Api/Events/MovePlayerToEventAnswer.ts @@ -0,0 +1,11 @@ +import * as tg from "generic-type-guard"; + +export const isMovePlayerToEventAnswer = new tg.IsInterface() + .withProperties({ + x: tg.isNumber, + y: tg.isNumber, + cancelled: tg.isBoolean, + }) + .get(); + +export type MovePlayerToEventAnswer = tg.GuardedType; diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index 0c71ae33..48de68d2 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -13,6 +13,12 @@ export const setPlayerName = (name: string) => { playerName = name; }; +let playerLanguage: string | undefined; + +export const setPlayerLanguage = (language: string | undefined) => { + playerLanguage = language; +}; + let tags: string[] | undefined; export const setTags = (_tags: string[]) => { @@ -61,6 +67,15 @@ export class WorkadventurePlayerCommands extends IframeApiContribution { + return await queryWorkadventure({ + type: "movePlayerTo", + data: { x, y, speed }, + }); + } + get userRoomToken(): string | undefined { if (userRoomToken === undefined) { throw new Error( diff --git a/front/src/Components/AudioManager/AudioManager.svelte b/front/src/Components/AudioManager/AudioManager.svelte index b62d8fbe..3385d6da 100644 --- a/front/src/Components/AudioManager/AudioManager.svelte +++ b/front/src/Components/AudioManager/AudioManager.svelte @@ -5,6 +5,7 @@ import { get } from "svelte/store"; import type { Unsubscriber } from "svelte/store"; import { onDestroy, onMount } from "svelte"; + import LL from "../../i18n/i18n-svelte"; let HTMLAudioPlayer: HTMLAudioElement; let audioPlayerVolumeIcon: HTMLElement; @@ -144,7 +145,7 @@
diff --git a/front/src/Components/Chat/Chat.svelte b/front/src/Components/Chat/Chat.svelte index 6827dde4..c4756a36 100644 --- a/front/src/Components/Chat/Chat.svelte +++ b/front/src/Components/Chat/Chat.svelte @@ -5,6 +5,7 @@ import ChatElement from "./ChatElement.svelte"; import { afterUpdate, beforeUpdate, onMount } from "svelte"; import { HtmlUtils } from "../../WebRtc/HtmlUtils"; + import LL from "../../i18n/i18n-svelte"; let listDom: HTMLElement; let chatWindowElement: HTMLElement; @@ -45,7 +46,7 @@

×

    -
  • Here is your chat history:

  • +
  • {$LL.chat.intro()}

  • {#each $chatMessagesStore as message, i}
  • {/each} diff --git a/front/src/Components/Chat/ChatMessageForm.svelte b/front/src/Components/Chat/ChatMessageForm.svelte index d57eaf5c..dd094394 100644 --- a/front/src/Components/Chat/ChatMessageForm.svelte +++ b/front/src/Components/Chat/ChatMessageForm.svelte @@ -1,4 +1,5 @@
      -
    • -
    • +
    • + +
    • +
    diff --git a/front/src/Components/selectCharacter/SelectCharacterScene.svelte b/front/src/Components/selectCharacter/SelectCharacterScene.svelte index 77db49e9..091f1d1d 100644 --- a/front/src/Components/selectCharacter/SelectCharacterScene.svelte +++ b/front/src/Components/selectCharacter/SelectCharacterScene.svelte @@ -1,6 +1,7 @@ + + + +X:
    +Y:
    +Speed:
    + + + + + + diff --git a/maps/tests/index.html b/maps/tests/index.html index 4a80634c..d27d7804 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -56,6 +56,14 @@ Test start tile (S2) + + + Success Failure Pending + + + Test moveTo parameter + + Success Failure Pending @@ -227,6 +235,14 @@ Test camera API + + + Success Failure Pending + + + Test Player Movement API + + Success Failure Pending diff --git a/maps/tests/move_to.json b/maps/tests/move_to.json new file mode 100644 index 00000000..796a9d46 --- /dev/null +++ b/maps/tests/move_to.json @@ -0,0 +1,275 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":10, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17], + "height":10, + "id":7, + "name":"walls", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 0, 0, 0, 0, 0, 0, 34, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":8, + "name":"meeting-room", + "opacity":1, + "properties":[ + { + "name":"startLayer", + "type":"bool", + "value":true + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":10, + "name":"start2", + "opacity":1, + "properties":[ + { + "name":"startLayer", + "type":"bool", + "value":true + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":33.4788210765457, + "id":1, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"Add \"moveTo\" parameter to the URL to make character move at game start.", + "wrap":true + }, + "type":"", + "visible":true, + "width":249.954975648686, + "x":35.2740564642832, + "y":34.4372323693377 + }, + { + "height":19.6921, + "id":3, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"...#start2&moveTo=meeting-room", + "wrap":true + }, + "type":"", + "visible":true, + "width":223.499265952492, + "x":32, + "y":114 + }, + { + "height":19.6921, + "id":4, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"start", + "wrap":true + }, + "type":"", + "visible":true, + "width":26.7596292501164, + "x":3.30880298090358, + "y":135.124359571495 + }, + { + "height":19.6921, + "id":5, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"...#start2&moveTo=start", + "wrap":true + }, + "type":"", + "visible":true, + "width":158.292, + "x":32, + "y":132 + }, + { + "height":19.6921, + "id":6, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"...#start&moveTo=meeting-room", + "wrap":true + }, + "type":"", + "visible":true, + "width":217.164845831393, + "x":32, + "y":93.740349627387 + }, + { + "height":19.6921, + "id":7, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"start2", + "wrap":true + }, + "type":"", + "visible":true, + "width":33.0940201210992, + "x":74.556855798789, + "y":245.393819585468 + }, + { + "height":19.6921, + "id":8, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"meeting-room", + "wrap":true + }, + "type":"", + "visible":true, + "width":92.7120717279925, + "x":233.848901257569, + "y":135.845612785282 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":11, + "nextobjectid":9, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.7.2", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tiles":[ + { + "id":16, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":17, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":18, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":19, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.6", + "width":10 +} \ No newline at end of file diff --git a/pusher/yarn.lock b/pusher/yarn.lock index bdafd193..1321a9c3 100644 --- a/pusher/yarn.lock +++ b/pusher/yarn.lock @@ -1622,9 +1622,11 @@ nice-try@^1.0.4: integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== node-fetch@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" nopt@^5.0.0: version "5.0.0" @@ -2265,6 +2267,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -2376,6 +2383,19 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" diff --git a/tests/tests/reconnect.ts b/tests/tests/reconnect.ts index 62f92fd2..9b17e339 100644 --- a/tests/tests/reconnect.ts +++ b/tests/tests/reconnect.ts @@ -3,7 +3,7 @@ import {assertLogMessage} from "./utils/log"; const fs = require('fs'); const Docker = require('dockerode'); import { Selector } from 'testcafe'; -import {login} from "./utils/roles"; +import {login, resetLanguage} from "./utils/roles"; import {findContainer, rebootBack, rebootPusher, resetRedis, startContainer, stopContainer} from "./utils/containers"; fixture `Reconnection` @@ -16,6 +16,8 @@ test("Test that connection can succeed even if WorkAdventure starts while pusher const errorMessage = Selector('.error-div'); + await resetLanguage('en-US'); + await t .navigateTo('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/mousewheel.json') .expect(errorMessage.innerText).contains('Unable to connect to WorkAdventure') diff --git a/tests/tests/translate.ts b/tests/tests/translate.ts new file mode 100644 index 00000000..deb5be0d --- /dev/null +++ b/tests/tests/translate.ts @@ -0,0 +1,34 @@ +import { Selector } from "testcafe"; +import { login } from "./utils/roles"; + +fixture`Translation` + .page`http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/mousewheel.json`; + +test("Test that I can switch to French", async (t: TestController) => { + const languageSelect = Selector(".languages-switcher"); + const languageOption = languageSelect.find("option"); + + await login( + t, + "http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/mousewheel.json" + ); + + await t + .click(".menuIcon img:first-child") + .click(Selector("button").withText("Settings")) + .click(".languages-switcher") + .click(languageOption.withText("Français (France)")) + .click(Selector("button").withText("Save")) + .wait(5000) + + .click(".menuIcon img:first-child") + .expect(Selector("button").withText("Paramètres").innerText) + .contains("Paramètres"); + + t.ctx.passed = true; +}).after(async (t) => { + if (!t.ctx.passed) { + console.log("Test failed. Browser logs:"); + console.log(await t.getBrowserConsoleMessages()); + } +}); diff --git a/tests/tests/utils/roles.ts b/tests/tests/utils/roles.ts index 7630f086..819446e7 100644 --- a/tests/tests/utils/roles.ts +++ b/tests/tests/utils/roles.ts @@ -1,6 +1,11 @@ -import { Role } from 'testcafe'; +import { Role, ClientFunction } from 'testcafe'; + +export const resetLanguage = ClientFunction((browserLanguage) => window.localStorage.setItem('language', browserLanguage)); + +export async function login(t: TestController, url: string, userName: string = "Alice", characterNumber: number = 2, browserLanguage: string|null = 'en-US') { + + await resetLanguage(browserLanguage); -export function login(t: TestController, url: string, userName: string = "Alice", characterNumber: number = 2) { t = t .navigateTo(url) .typeText('input[name="loginSceneName"]', userName)