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/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 442661bf..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 ``` 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/iframe/player.ts b/front/src/Api/iframe/player.ts index cf10dfa5..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
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 @@
      -
    • -
    • +
    • + +
    • +