feat: [i18n] inline translations with english default

This commit is contained in:
David Urvoy 2021-05-28 23:13:41 +00:00 committed by David Urvoy
parent 95924f7572
commit 51062c7821
17 changed files with 164 additions and 296 deletions

View file

@ -1,168 +1,2 @@
{ {
"menu": {
"grimoire": "Grimoire",
"zoom": "Zoom",
"other-players-in-session": "other players in this session",
"show-background-image": "Show background image",
"background-image": "Background image",
"show-custom-image": "Show custom image",
"night-order": "Night order",
"disable-animations": "Disable animations",
"mute-sounds": "Mute sounds",
"playing": "Playing",
"hosting": "Hosting",
"switch-night": "Switch to night",
"switch-day": "Switch to day",
"show": "Show",
"hide": "Hide",
"live-session": "Live Session",
"delay-to": "Delay to ",
"vote-history": "Vote history",
"leave-session": "Leave Session",
"players": "Players",
"add": "Add",
"randomize": "Randomize",
"remove-all": "Remove all",
"characters": "Characters",
"select-edition": "Select Edition",
"choose-assign": "Choose & Assign",
"add-fabled": "Add Fabled",
"help": "Help",
"reference-sheet": "Reference Sheet",
"night-order-sheet": "Night Order Sheet",
"game-state-json": "Game State JSON",
"join-discord": "Join Discord",
"source-code": "Source code",
"host": "Host (Storyteller)",
"join": "Join (Player)",
"copy-player-link": "Copy player link",
"send-characters": "Send characters",
"player-name": "Player name",
"enter-background": "Enter custom background URL",
"create-session": "Enter a channel number / name for your session",
"distribute-roles": "Do you want to distribute assigned characters to all SEATED players?",
"custom-images-warn": "Are you sure you want to allow custom images? A malicious script file author might track your IP address this way.",
"join-session": "Enter the channel number / name of the session you want to join",
"leave-warn": "Are you sure you want to leave the active live game?",
"randomize-warn": "Are you sure you want to randomize seatings?",
"remove-players-warn": "Are you sure you want to remove all players?",
"remove-roles-warn": "Are you sure you want to remove all player roles?"
},
"intro": {
"welcome": "Welcome to the (unofficial) {grimoire} for Blood on the Clocktower ! Please add more players through the {menu} on the top right or by pressing {a-key}. You can also join a game session by pressing {j-key}.",
"townsquare-grimoire": "Virtual Town Square and Grimoire",
"menu": "Menu",
"project-notice": "This project is free and open source and can be found on {github}. It is not affiliated with The Pandemonium Institute. \"Blood on the Clocktower\" is a trademark of Steven Medway and The Pandemonium Institute."
},
"player": {
"change-pronouns": "Change Pronouns",
"rename": "Rename",
"move-player": "Move player",
"swap-seats": "Swap seats",
"remove": "Remove",
"empty-seat": "Empty seat",
"nomination": "Nomination",
"claim-seat": "Claim seat",
"vacate-seat": "Vacate seat",
"seat-occupied": "Seat occupied",
"player-name": "Player name"
},
"town-info": {
"add-more": "Please add more players!",
"night-phase": "Night phase"
},
"townsquare": {
"other-characters": "Other characters",
"demon-bluffs": "Demon bluffs",
"fabled": "Fabled"
},
"vote": {
"nominated": "nominated",
"vote": "vote",
"in-favor": "in favor",
"majority-is": "majority is",
"time-per-player": "Time per player",
"countdown": "Countdown",
"restart": "Restart",
"start": "Start",
"pause": "Pause",
"resume": "Resume",
"reset": "Reset",
"close": "Close",
"mark-execution": "Mark for execution",
"clear-mark": "Clear mark",
"seconds-between-votes": "seconds between votes",
"hand-down": "Hand DOWN",
"hand-up": "Hand UP",
"claim-seat": "Please claim a seat to vote"
},
"edition-modal": {
"select-edition": "Select an edition:",
"custom-script": "Custom Script / Characters",
"load-script-title": "Load custom script / characters",
"load-script-help": "To play with a custom script, you need to select the characters you want to play with in the official {link} and then upload the generated \"custom-list.json\" either directly here or provide a URL to such a hosted JSON file.",
"script-tool": "Script Tool",
"custom-characters-notice": "To play with custom characters, please read {documentation} on how to write a custom character definition file.",
"documentation": "the documentation",
"trusted-sources": "Only load custom JSON files from sources that you trust!",
"popular-scripts": "Some popular custom scripts:",
"upload-json": "Upload JSON",
"enter-url": "Enter URL",
"back": "Back"
},
"fabled-modal": {
"choose-fabled": "Choose a fabled character to add to the game"
},
"game-state-modal": {
"current-game-state": "Current Game State",
"copy-json": "Copy JSON",
"load-state": "Load State"
},
"night-order-modal": {
"show-reference": "Show Character Reference",
"night-order": "Night Order",
"first-night": "First Night",
"other-nights": "Other Nights",
"minion-info": "Minion info",
"minion-reminder": "• If more than one Minion, they all make eye contact with each other. • Show the “This is the Demon” card. Point to the Demon.",
"demon-info": "Demon info & bluffs",
"demon-reminder": "• Show the “These are your minions” card. Point to each Minion. • Show the “These characters are not in play” card. Show 3 character tokens of good characters not in play."
},
"reference-modal": {
"show-night-order": "Show Night Order",
"character-reference": "Character Reference",
"jinxed": "Jinxed",
"townsfolk": "Townsfolk",
"outsider": "Outsider",
"minion": "Minion",
"demon": "Demon"
},
"reminder-modal": {
"reminder-token": "Choose a reminder token"
},
"role-modal": {
"choose-character": "Choose a new character for {player}",
"bluffing": "bluffing",
"edition-roles": "Edition Roles",
"other-travelers": "Other Travelers"
},
"roles-modal": {
"select-roles": "Select the characters for {nonTravelers} players:",
"warning-incorrect-setup": "Warning: there are characters selected that modify the game setup! The randomizer does not account for these characters.",
"allow-duplicate": "Allow duplicate characters",
"assign-randomly": "Assign {roles} characters randomly",
"shuffle": "Shuffle characters"
},
"vote-history-modal": {
"vote-history": "Vote history",
"accessible-players": "Accessible to players",
"clear": "Clear for everyone",
"time": "Time",
"nominator": "Nominator",
"nominee": "Nominee",
"type": "Type",
"votes": "Votes",
"majority": "Majority",
"voters": "Voters"
}
} }

View file

@ -1,14 +1,16 @@
<template> <template>
<div class="intro"> <div class="intro">
<img src="static/apple-icon.png" alt="" /> <img src="static/apple-icon.png" alt="" />
<i18n path="intro.welcome"> <i18n
path="Welcome to the (unofficial) {grimoire} for Blood on the Clocktower ! Please add more players through the {menu} on the top right or by pressing {a-key}. You can also join a game session by pressing {j-key}."
>
<template #menu> <template #menu>
<span class="button" @click="toggleMenu"> <span class="button" @click="toggleMenu">
<font-awesome-icon icon="cog" /> {{ $t("intro.menu") }}</span <font-awesome-icon icon="cog" /> {{ $t("Menu") }}</span
> >
</template> </template>
<template #grimoire> <template #grimoire>
<b>{{ $t("intro.townsquare-grimoire") }}</b> <b>{{ $t("Virtual Town Square and Grimoire") }}</b>
</template> </template>
<template #a-key> <template #a-key>
<b>[A]</b> <b>[A]</b>
@ -19,7 +21,9 @@
</i18n> </i18n>
<br /> <br />
<div class="footer"> <div class="footer">
<i18n path="intro.project-notice"> <i18n
path='This project is free and open source and can be found on {github}. It is not affiliated with The Pandemonium Institute. "Blood on the Clocktower" is a trademark of Steven Medway and The Pandemonium Institute.'
>
<template #github> <template #github>
<a href="https://github.com/bra1n/townsquare" target="_blank" <a href="https://github.com/bra1n/townsquare" target="_blank"
>GitHub</a >GitHub</a

View file

@ -22,7 +22,7 @@
v-if="session.sessionId" v-if="session.sessionId"
@click="leaveSession" @click="leaveSession"
:title=" :title="
`${session.playerCount} ${$t('other-players-in-session')}${ `${session.playerCount} ${$t('other players in this session')}${
session.ping ? ' (' + session.ping + 'ms latency)' : '' session.ping ? ' (' + session.ping + 'ms latency)' : ''
}` }`
" "
@ -47,23 +47,23 @@
<template v-if="tab === 'grimoire'"> <template v-if="tab === 'grimoire'">
<!-- Grimoire --> <!-- Grimoire -->
<li class="headline">{{ $t("menu.grimoire") }}</li> <li class="headline">{{ $t("Grimoire") }}</li>
<li @click="toggleGrimoire" v-if="players.length"> <li @click="toggleGrimoire" v-if="players.length">
<template v-if="!grimoire.isPublic">{{ $t("menu.hide") }}</template> <template v-if="!grimoire.isPublic">{{ $t("Hide") }}</template>
<template v-if="grimoire.isPublic">{{ $t("menu.show") }}</template> <template v-if="grimoire.isPublic">{{ $t("Show") }}</template>
<em>[G]</em> <em>[G]</em>
</li> </li>
<li @click="toggleNight" v-if="!session.isSpectator"> <li @click="toggleNight" v-if="!session.isSpectator">
<template v-if="!grimoire.isNight"> <template v-if="!grimoire.isNight">
{{ $t("menu.switch-night") }} {{ $t("Switch to Night") }}
</template> </template>
<template v-if="grimoire.isNight"> <template v-if="grimoire.isNight">
{{ $t("menu.switch-day") }} {{ $t("Switch to Day") }}
</template> </template>
<em>[S]</em> <em>[S]</em>
</li> </li>
<li @click="toggleNightOrder" v-if="players.length"> <li @click="toggleNightOrder" v-if="players.length">
{{ $t("menu.night-order") }} {{ $t("Night order") }}
<em> <em>
<font-awesome-icon <font-awesome-icon
:icon="[ :icon="[
@ -74,7 +74,7 @@
</em> </em>
</li> </li>
<li v-if="players.length"> <li v-if="players.length">
{{ $t("menu.zoom") }} {{ $t("Zoom") }}
<em> <em>
<font-awesome-icon <font-awesome-icon
@click="setZoom(grimoire.zoom - 1)" @click="setZoom(grimoire.zoom - 1)"
@ -88,11 +88,11 @@
</em> </em>
</li> </li>
<li @click="setBackground"> <li @click="setBackground">
{{ $t("menu.background-image") }} {{ $t("Background image") }}
<em><font-awesome-icon icon="image"/></em> <em><font-awesome-icon icon="image"/></em>
</li> </li>
<li v-if="!edition.isOfficial" @click="imageOptIn"> <li v-if="!edition.isOfficial" @click="imageOptIn">
<small>{{ $t("menu.show-custom-image") }}</small> <small>{{ $t("Show custom image") }}</small>
<em <em
><font-awesome-icon ><font-awesome-icon
:icon="[ :icon="[
@ -102,14 +102,14 @@
/></em> /></em>
</li> </li>
<li @click="toggleStatic"> <li @click="toggleStatic">
{{ $t("menu.disable-animations") }} {{ $t("Disable Animations") }}
<em <em
><font-awesome-icon ><font-awesome-icon
:icon="['fas', grimoire.isStatic ? 'check-square' : 'square']" :icon="['fas', grimoire.isStatic ? 'check-square' : 'square']"
/></em> /></em>
</li> </li>
<li @click="toggleMuted"> <li @click="toggleMuted">
{{ $t("menu.mute-sounds") }} {{ $t("Mute Sounds") }}
<em <em
><font-awesome-icon ><font-awesome-icon
:icon="['fas', grimoire.isMuted ? 'volume-mute' : 'volume-up']" :icon="['fas', grimoire.isMuted ? 'volume-mute' : 'volume-up']"
@ -120,37 +120,39 @@
<template v-if="tab === 'session'"> <template v-if="tab === 'session'">
<!-- Session --> <!-- Session -->
<li class="headline" v-if="session.sessionId"> <li class="headline" v-if="session.sessionId">
{{ session.isSpectator ? $t("menu.playing") : $t("menu.hosting") }} {{ session.isSpectator ? $t("Playing") : $t("Hosting") }}
</li> </li>
<li class="headline" v-else> <li class="headline" v-else>
{{ $t("menu.live-session") }} {{ $t("Live Session") }}
</li> </li>
<template v-if="!session.sessionId"> <template v-if="!session.sessionId">
<li @click="hostSession">{{ $t("menu.host") }}<em>[H]</em></li> <li @click="hostSession">
<li @click="joinSession">{{ $t("menu.join") }}<em>[J]</em></li> {{ $t("Host (Storyteller)") }}<em>[H]</em>
</li>
<li @click="joinSession">{{ $t("Join (Player)") }}<em>[J]</em></li>
</template> </template>
<template v-else> <template v-else>
<li v-if="session.ping"> <li v-if="session.ping">
{{ $t("menu.delay-to") }} {{ $t("Delay to ") }}
{{ session.isSpectator ? "host" : "players" }} {{ session.isSpectator ? "host" : "players" }}
<em>{{ session.ping }}ms</em> <em>{{ session.ping }}ms</em>
</li> </li>
<li @click="copySessionUrl"> <li @click="copySessionUrl">
{{ $t("menu.copy-player-link") }} {{ $t("Copy player link") }}
<em><font-awesome-icon icon="copy"/></em> <em><font-awesome-icon icon="copy"/></em>
</li> </li>
<li v-if="!session.isSpectator" @click="distributeRoles"> <li v-if="!session.isSpectator" @click="distributeRoles">
{{ $t("menu.send-characters") }} {{ $t("Send Characters") }}
<em><font-awesome-icon icon="theater-masks"/></em> <em><font-awesome-icon icon="theater-masks"/></em>
</li> </li>
<li <li
v-if="session.voteHistory.length || !session.isSpectator" v-if="session.voteHistory.length || !session.isSpectator"
@click="toggleModal('voteHistory')" @click="toggleModal('voteHistory')"
> >
{{ $t("menu.vote-history") }}<em>[V]</em> {{ $t("Vote history") }}<em>[V]</em>
</li> </li>
<li @click="leaveSession"> <li @click="leaveSession">
{{ $t("menu.leave-session") }} {{ $t("Leave Session") }}
<em>{{ session.sessionId }}</em> <em>{{ session.sessionId }}</em>
</li> </li>
</template> </template>
@ -158,62 +160,62 @@
<template v-if="tab === 'players' && !session.isSpectator"> <template v-if="tab === 'players' && !session.isSpectator">
<!-- Users --> <!-- Users -->
<li class="headline">{{ $t("menu.players") }}</li> <li class="headline">{{ $t("Players") }}</li>
<li @click="addPlayer" v-if="players.length < 20"> <li @click="addPlayer" v-if="players.length < 20">
{{ $t("menu.add") }}<em>[A]</em> {{ $t("Add") }}<em>[A]</em>
</li> </li>
<li @click="randomizeSeatings" v-if="players.length > 2"> <li @click="randomizeSeatings" v-if="players.length > 2">
{{ $t("menu.randomize") }} {{ $t("Randomize") }}
<em><font-awesome-icon icon="dice"/></em> <em><font-awesome-icon icon="dice"/></em>
</li> </li>
<li @click="clearPlayers" v-if="players.length"> <li @click="clearPlayers" v-if="players.length">
{{ $t("menu.remove-all") }} {{ $t("Remove all") }}
<em><font-awesome-icon icon="trash-alt"/></em> <em><font-awesome-icon icon="trash-alt"/></em>
</li> </li>
</template> </template>
<template v-if="tab === 'characters'"> <template v-if="tab === 'characters'">
<!-- Characters --> <!-- Characters -->
<li class="headline">{{ $t("menu.characters") }}</li> <li class="headline">{{ $t("Characters") }}</li>
<li v-if="!session.isSpectator" @click="toggleModal('edition')"> <li v-if="!session.isSpectator" @click="toggleModal('edition')">
{{ $t("menu.select-edition") }} {{ $t("Select Edition") }}
<em>[E]</em> <em>[E]</em>
</li> </li>
<li <li
@click="toggleModal('roles')" @click="toggleModal('roles')"
v-if="!session.isSpectator && players.length > 4" v-if="!session.isSpectator && players.length > 4"
> >
{{ $t("menu.choose-assign") }} {{ $t("Choose & Assign") }}
<em>[C]</em> <em>[C]</em>
</li> </li>
<li v-if="!session.isSpectator" @click="toggleModal('fabled')"> <li v-if="!session.isSpectator" @click="toggleModal('fabled')">
{{ $t("menu.add-fabled") }} {{ $t("Add Fabled") }}
<em><font-awesome-icon icon="dragon"/></em> <em><font-awesome-icon icon="dragon"/></em>
</li> </li>
<li @click="clearRoles" v-if="players.length"> <li @click="clearRoles" v-if="players.length">
{{ $t("menu.remove-all") }} {{ $t("Remove all") }}
<em><font-awesome-icon icon="trash-alt"/></em> <em><font-awesome-icon icon="trash-alt"/></em>
</li> </li>
</template> </template>
<template v-if="tab === 'help'"> <template v-if="tab === 'help'">
<!-- Help --> <!-- Help -->
<li class="headline">{{ $t("menu.help") }}</li> <li class="headline">{{ $t("Help") }}</li>
<li @click="toggleModal('reference')"> <li @click="toggleModal('reference')">
{{ $t("menu.reference-sheet") }} {{ $t("Reference Sheet") }}
<em>[R]</em> <em>[R]</em>
</li> </li>
<li @click="toggleModal('nightOrder')"> <li @click="toggleModal('nightOrder')">
{{ $t("menu.night-order-sheet") }} {{ $t("Night Order Sheet") }}
<em>[N]</em> <em>[N]</em>
</li> </li>
<li @click="toggleModal('gameState')"> <li @click="toggleModal('gameState')">
{{ $t("menu.game-state-json") }} {{ $t("Game State JSON") }}
<em><font-awesome-icon icon="file-code"/></em> <em><font-awesome-icon icon="file-code"/></em>
</li> </li>
<li> <li>
<a href="https://discord.gg/Gd7ybwWbFk" target="_blank"> <a href="https://discord.gg/Gd7ybwWbFk" target="_blank">
{{ $t("menu.join-discord") }} {{ $t("Join Discord") }}
</a> </a>
<em> <em>
<a href="https://discord.gg/Gd7ybwWbFk" target="_blank"> <a href="https://discord.gg/Gd7ybwWbFk" target="_blank">
@ -223,7 +225,7 @@
</li> </li>
<li> <li>
<a href="https://github.com/bra1n/townsquare" target="_blank"> <a href="https://github.com/bra1n/townsquare" target="_blank">
{{ $t("menu.source-code") }} {{ $t("Source code") }}
</a> </a>
<em> <em>
<a href="https://github.com/bra1n/townsquare" target="_blank"> <a href="https://github.com/bra1n/townsquare" target="_blank">
@ -252,7 +254,7 @@ export default {
}, },
methods: { methods: {
setBackground() { setBackground() {
const background = prompt(this.$t("menu.enter-background")); const background = prompt(this.$t("Enter custom background URL"));
if (background || background === "") { if (background || background === "") {
this.$store.commit("setBackground", background); this.$store.commit("setBackground", background);
} }
@ -260,7 +262,7 @@ export default {
hostSession() { hostSession() {
if (this.session.sessionId) return; if (this.session.sessionId) return;
const sessionId = prompt( const sessionId = prompt(
this.$t("menu.create-session"), this.$t("Enter a channel number / name for your session"),
Math.round(Math.random() * 10000) Math.round(Math.random() * 10000)
); );
if (sessionId) { if (sessionId) {
@ -277,7 +279,9 @@ export default {
}, },
distributeRoles() { distributeRoles() {
if (this.session.isSpectator) return; if (this.session.isSpectator) return;
const popup = this.$t("menu.distribute-roles"); const popup = this.$t(
"Do you want to distribute assigned characters to all SEATED players?"
);
if (confirm(popup)) { if (confirm(popup)) {
this.$store.commit("session/distributeRoles", true); this.$store.commit("session/distributeRoles", true);
setTimeout( setTimeout(
@ -289,14 +293,20 @@ export default {
} }
}, },
imageOptIn() { imageOptIn() {
const popup = this.$t("menu.custom-images-warn"); const popup = this.$t(
"Are you sure you want to allow custom images? A malicious script file author might track your IP address this way."
);
if (this.grimoire.isImageOptIn || confirm(popup)) { if (this.grimoire.isImageOptIn || confirm(popup)) {
this.toggleImageOptIn(); this.toggleImageOptIn();
} }
}, },
joinSession() { joinSession() {
if (this.session.sessionId) return this.leaveSession(); if (this.session.sessionId) return this.leaveSession();
let sessionId = prompt(this.$t("menu.join-session")); let sessionId = prompt(
this.$t(
"Enter the channel number / name of the session you want to join"
)
);
if (sessionId.match(/^https?:\/\//i)) { if (sessionId.match(/^https?:\/\//i)) {
sessionId = sessionId.split("#").pop(); sessionId = sessionId.split("#").pop();
} }
@ -308,7 +318,9 @@ export default {
} }
}, },
leaveSession() { leaveSession() {
if (confirm(this.$t("menu.leave-warn"))) { if (
confirm(this.$t("Are you sure you want to leave the active live game?"))
) {
this.$store.commit("session/setSpectator", false); this.$store.commit("session/setSpectator", false);
this.$store.commit("session/setSessionId", ""); this.$store.commit("session/setSessionId", "");
} }
@ -316,20 +328,20 @@ export default {
addPlayer() { addPlayer() {
if (this.session.isSpectator) return; if (this.session.isSpectator) return;
if (this.players.length >= 20) return; if (this.players.length >= 20) return;
const name = prompt(this.$t("menu.player-name")); const name = prompt(this.$t("Player name"));
if (name) { if (name) {
this.$store.commit("players/add", name); this.$store.commit("players/add", name);
} }
}, },
randomizeSeatings() { randomizeSeatings() {
if (this.session.isSpectator) return; if (this.session.isSpectator) return;
if (confirm(this.$t("menu.randomize-warn"))) { if (confirm(this.$t("Are you sure you want to randomize seatings?"))) {
this.$store.dispatch("players/randomize"); this.$store.dispatch("players/randomize");
} }
}, },
clearPlayers() { clearPlayers() {
if (this.session.isSpectator) return; if (this.session.isSpectator) return;
if (confirm(this.$t("menu.remove-players-warn"))) { if (confirm(this.$t("Are you sure you want to remove all players?"))) {
// abort vote if in progress // abort vote if in progress
if (this.session.nomination) { if (this.session.nomination) {
this.$store.commit("session/nomination"); this.$store.commit("session/nomination");
@ -338,7 +350,9 @@ export default {
} }
}, },
clearRoles() { clearRoles() {
if (confirm(this.$t("menu.remove-roles-warn"))) { if (
confirm(this.$t("Are you sure you want to remove all player roles?"))
) {
this.$store.dispatch("players/clearRoles"); this.$store.dispatch("players/clearRoles");
} }
}, },

View file

@ -125,36 +125,36 @@
" "
> >
<font-awesome-icon icon="venus-mars" /> <font-awesome-icon icon="venus-mars" />
{{ $t("player.change-pronouns") }} {{ $t("Change Pronouns") }}
</li> </li>
<template v-if="!session.isSpectator"> <template v-if="!session.isSpectator">
<li @click="changeName"> <li @click="changeName">
<font-awesome-icon icon="user-edit" /> <font-awesome-icon icon="user-edit" />
{{ $t("player.rename") }} {{ $t("Rename") }}
</li> </li>
<li @click="movePlayer()" :class="{ disabled: session.lockedVote }"> <li @click="movePlayer()" :class="{ disabled: session.lockedVote }">
<font-awesome-icon icon="redo-alt" /> <font-awesome-icon icon="redo-alt" />
{{ $t("player.move-player") }} {{ $t("Move player") }}
</li> </li>
<li @click="swapPlayer()" :class="{ disabled: session.lockedVote }"> <li @click="swapPlayer()" :class="{ disabled: session.lockedVote }">
<font-awesome-icon icon="exchange-alt" /> <font-awesome-icon icon="exchange-alt" />
{{ $t("player.swap-seats") }} {{ $t("Swap seats") }}
</li> </li>
<li @click="removePlayer" :class="{ disabled: session.lockedVote }"> <li @click="removePlayer" :class="{ disabled: session.lockedVote }">
<font-awesome-icon icon="times-circle" /> <font-awesome-icon icon="times-circle" />
{{ $t("player.remove") }} {{ $t("Remove") }}
</li> </li>
<li <li
@click="updatePlayer('id', '', true)" @click="updatePlayer('id', '', true)"
v-if="player.id && session.sessionId" v-if="player.id && session.sessionId"
> >
<font-awesome-icon icon="chair" /> <font-awesome-icon icon="chair" />
{{ $t("player.empty-seat") }} {{ $t("Empty seat") }}
</li> </li>
<template v-if="!session.nomination"> <template v-if="!session.nomination">
<li @click="nominatePlayer()"> <li @click="nominatePlayer()">
<font-awesome-icon icon="hand-point-right" /> <font-awesome-icon icon="hand-point-right" />
{{ $t("player.nomination") }} {{ $t("Nomination") }}
</li> </li>
</template> </template>
</template> </template>
@ -165,12 +165,12 @@
> >
<font-awesome-icon icon="chair" /> <font-awesome-icon icon="chair" />
<template v-if="!player.id"> <template v-if="!player.id">
{{ $t("player.claim-seat") }} {{ $t("Claim seat") }}
</template> </template>
<template v-else-if="player.id === session.playerId"> <template v-else-if="player.id === session.playerId">
{{ $t("player.vacate-seat") }} {{ $t("Vacate seat") }}
</template> </template>
<template v-else> {{ $t("player.seat-occupied") }}</template> <template v-else> {{ $t("Seat occupied") }}</template>
</li> </li>
</ul> </ul>
</transition> </transition>
@ -289,7 +289,7 @@ export default {
changeName() { changeName() {
if (this.session.isSpectator) return; if (this.session.isSpectator) return;
const name = const name =
prompt(this.$t("player.player-name"), this.player.name) || this.player.name; prompt(this.$t("Player name"), this.player.name) || this.player.name;
this.updatePlayer("name", name, true); this.updatePlayer("name", name, true);
}, },
removeReminder(reminder) { removeReminder(reminder) {

View file

@ -12,7 +12,7 @@
}" }"
></li> ></li>
<li v-if="players.length - teams.traveler < 5"> <li v-if="players.length - teams.traveler < 5">
{{ $t("town-info.add-more") }} {{ $t("Please add more players!") }}
</li> </li>
<li> <li>
<span class="meta" v-if="!edition.isOfficial"> <span class="meta" v-if="!edition.isOfficial">
@ -64,7 +64,7 @@
/> />
</span> </span>
<span v-if="grimoire.isNight"> <span v-if="grimoire.isNight">
{{ $t("town-info.night-phase") }} {{ $t("Night phase") }}
<font-awesome-icon :icon="['fas', 'cloud-moon']" /> <font-awesome-icon :icon="['fas', 'cloud-moon']" />
</span> </span>
</li> </li>

View file

@ -31,10 +31,10 @@
> >
<h3> <h3>
<span v-if="session.isSpectator"> <span v-if="session.isSpectator">
{{ $t("townsquare.other-characters") }} {{ $t("Other characters") }}
</span> </span>
<span v-else> <span v-else>
{{ $t("townsquare.demon-bluffs") }} {{ $t("Demon bluffs") }}
</span> </span>
<font-awesome-icon icon="times-circle" @click.stop="toggleBluffs" /> <font-awesome-icon icon="times-circle" @click.stop="toggleBluffs" />
<font-awesome-icon icon="plus-circle" @click.stop="toggleBluffs" /> <font-awesome-icon icon="plus-circle" @click.stop="toggleBluffs" />
@ -53,7 +53,7 @@
<div class="fabled" :class="{ closed: !isFabledOpen }" v-if="fabled.length"> <div class="fabled" :class="{ closed: !isFabledOpen }" v-if="fabled.length">
<h3> <h3>
<span> <span>
{{ $t("townsquare.fabled") }} {{ $t("Fabled") }}
</span> </span>
<font-awesome-icon icon="times-circle" @click.stop="toggleFabled" /> <font-awesome-icon icon="times-circle" @click.stop="toggleFabled" />
<font-awesome-icon icon="plus-circle" @click.stop="toggleFabled" /> <font-awesome-icon icon="plus-circle" @click.stop="toggleFabled" />

View file

@ -6,24 +6,24 @@
</div> </div>
<div class="overlay"> <div class="overlay">
<audio src="../assets/sounds/countdown.mp3" preload="auto"></audio> <audio src="../assets/sounds/countdown.mp3" preload="auto"></audio>
<em class="blue">{{ nominator.name }}</em> {{ $t("vote.nominated") }} <em class="blue">{{ nominator.name }}</em> {{ $t("nominated") }}
<em>{{ nominee.name }}</em <em>{{ nominee.name }}</em
>! >!
<br /> <br />
<em class="blue"> <em class="blue">
{{ voters.length }} {{ $t("vote.vote") }}{{ voters.length !== 1 ? "s" : "" }} {{ voters.length }} {{ $t("vote") }}{{ voters.length !== 1 ? "s" : "" }}
</em> </em>
{{ $t("vote.in-favor") }} {{ $t("in favor") }}
<em v-if="nominee.role.team !== 'traveler'"> <em v-if="nominee.role.team !== 'traveler'">
({{ $t("vote.majority-is") }} {{ Math.ceil(alive / 2) }}) ({{ $t("majority is") }} {{ Math.ceil(alive / 2) }})
</em> </em>
<em v-else> <em v-else>
({{ $t("vote.majority-is") }} {{ Math.ceil(players.length / 2) }}) ({{ $t("majority is") }} {{ Math.ceil(players.length / 2) }})
</em> </em>
<template v-if="!session.isSpectator"> <template v-if="!session.isSpectator">
<div v-if="!session.isVoteInProgress && session.lockedVote < 1"> <div v-if="!session.isVoteInProgress && session.lockedVote < 1">
{{ $t("vote.time-per-player") }}: {{ $t("Time per player") }}:
<font-awesome-icon <font-awesome-icon
@mousedown.prevent="setVotingSpeed(-500)" @mousedown.prevent="setVotingSpeed(-500)"
icon="minus-circle" icon="minus-circle"
@ -40,10 +40,10 @@
v-if="!session.isVoteInProgress" v-if="!session.isVoteInProgress"
@click="countdown" @click="countdown"
> >
{{ $t("vote.countdown") }} {{ $t("Countdown") }}
</div> </div>
<div class="button" v-if="!session.isVoteInProgress" @click="start"> <div class="button" v-if="!session.isVoteInProgress" @click="start">
{{ session.lockedVote ? $t("vote.restart") : $t("vote.start") }} {{ session.lockedVote ? $t("Restart") : $t("Start") }}
</div> </div>
<template v-else> <template v-else>
<div <div
@ -51,11 +51,11 @@
:class="{ disabled: !session.lockedVote }" :class="{ disabled: !session.lockedVote }"
@click="pause" @click="pause"
> >
{{ voteTimer ? $t("vote.pause") : $t("vote.resume") }} {{ voteTimer ? $t("Pause") : $t("Resume") }}
</div> </div>
<div class="button" @click="stop">{{ $t("vote.reset") }}</div> <div class="button" @click="stop">{{ $t("Reset") }}</div>
</template> </template>
<div class="button demon" @click="finish">{{ $t("vote.close") }}</div> <div class="button demon" @click="finish">{{ $t("Close") }}</div>
</div> </div>
<div class="button-group mark" v-if="nominee.role.team !== 'traveler'"> <div class="button-group mark" v-if="nominee.role.team !== 'traveler'">
<div <div
@ -65,16 +65,16 @@
}" }"
@click="setMarked" @click="setMarked"
> >
{{ $t("vote.mark-execution") }} {{ $t("Mark for execution") }}
</div> </div>
<div class="button" @click="removeMarked"> <div class="button" @click="removeMarked">
{{ $t("vote.clear-mark") }} {{ $t("Clear mark") }}
</div> </div>
</div> </div>
</template> </template>
<template v-else-if="canVote"> <template v-else-if="canVote">
<div v-if="!session.isVoteInProgress"> <div v-if="!session.isVoteInProgress">
{{ session.votingSpeed / 1000 }} {{ $t("vote.seconds-between-votes") }} {{ session.votingSpeed / 1000 }} {{ $t("seconds between votes") }}
</div> </div>
<div class="button-group"> <div class="button-group">
<div <div
@ -82,18 +82,18 @@
@click="vote(false)" @click="vote(false)"
:class="{ disabled: !currentVote }" :class="{ disabled: !currentVote }"
> >
{{ $t("vote.hand-down") }} {{ $t("Hand DOWN") }}
</div> </div>
<div <div
class="button demon" class="button demon"
@click="vote(true)" @click="vote(true)"
:class="{ disabled: currentVote }" :class="{ disabled: currentVote }"
> >
{{ $t("vote.hand-up") }} {{ $t("Hand UP") }}
</div> </div>
</div> </div>
</template> </template>
<div v-else-if="!player">{{ $t("vote.claim-seat") }}.</div> <div v-else-if="!player">{{ $t("Please claim a seat to vote") }}.</div>
</div> </div>
<transition name="blur"> <transition name="blur">
<div <div

View file

@ -1,7 +1,7 @@
<template> <template>
<Modal class="editions" v-if="modals.edition" @close="toggleModal('edition')"> <Modal class="editions" v-if="modals.edition" @close="toggleModal('edition')">
<div v-if="!isCustom"> <div v-if="!isCustom">
<h3>{{ $t("edition-modal.select-edition") }}</h3> <h3>{{ $t("Select an edition:") }}</h3>
<ul class="editions"> <ul class="editions">
<li <li
v-for="edition in editions" v-for="edition in editions"
@ -24,34 +24,41 @@
backgroundImage: `url(${require('../../assets/editions/custom.png')})` backgroundImage: `url(${require('../../assets/editions/custom.png')})`
}" }"
> >
{{ $t("edition-modal.custom-script") }} {{ $t("Custom Script / Characters") }}
</li> </li>
</ul> </ul>
</div> </div>
<div class="custom" v-else> <div class="custom" v-else>
<h3>{{ $t("edition-modal.load-script-title") }}</h3> <h3>{{ $t("Load custom script / characters") }}</h3>
<i18n path="edition-modal.load-script-help" tag="span"> <i18n
path='To play with a custom script, you need to select the characters you want to play with in the official {link} and then upload the generated "custom-list.json" either directly here or provide a URL to such a hosted JSON file.'
tag="span"
>
<template #link> <template #link>
<a <a
href="https://bloodontheclocktower.com/script-tool/" href="https://bloodontheclocktower.com/script-tool/"
target="_blank" target="_blank"
>{{ $t("edition-modal.script-tool") }}</a >{{ $t("Script Tool") }}</a
> >
</template> </template>
</i18n> </i18n>
<br /> <br />
<br /> <br />
<i18n path="edition-modal.custom-characters-notice"> <i18n
path="To play with custom characters, please read {documentation} on how to write a custom character definition file."
>
<template #documentation> <template #documentation>
<a <a
href="https://github.com/bra1n/townsquare#custom-characters" href="https://github.com/bra1n/townsquare#custom-characters"
target="_blank" target="_blank"
>{{ $t("edition-modal.documentation") }}</a >{{ $t("the documentation") }}</a
> >
</template> </template>
</i18n> </i18n>
<b> {{ $t("edition-modal.trusted-sources") }}</b> <b>
<h3>{{ $t("edition-modal.popular-scripts") }}</h3> {{ $t("Only load custom JSON files from sources that you trust!") }}</b
>
<h3>{{ $t("Some popular custom scripts:") }}</h3>
<ul class="scripts"> <ul class="scripts">
<li <li
v-for="(script, index) in scripts" v-for="(script, index) in scripts"
@ -69,13 +76,13 @@
/> />
<div class="button-group"> <div class="button-group">
<div class="button" @click="openUpload"> <div class="button" @click="openUpload">
<font-awesome-icon icon="file-upload" /> {{ $t("edition-modal.upload-json") }} <font-awesome-icon icon="file-upload" /> {{ $t("Upload JSON") }}
</div> </div>
<div class="button" @click="promptURL"> <div class="button" @click="promptURL">
<font-awesome-icon icon="link" /> {{ $t("edition-modal.enter-url") }} <font-awesome-icon icon="link" /> {{ $t("Enter URL") }}
</div> </div>
<div class="button" @click="isCustom = false"> <div class="button" @click="isCustom = false">
<font-awesome-icon icon="undo" /> {{ $t("edition-modal.back") }} <font-awesome-icon icon="undo" /> {{ $t("Back") }}
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,7 +1,7 @@
<template> <template>
<Modal v-if="modals.fabled && fabled.length" @close="toggleModal('fabled')"> <Modal v-if="modals.fabled && fabled.length" @close="toggleModal('fabled')">
<h3> <h3>
{{ $t("fabled-modal.choose-fabled") }} {{ $t("Choose a fabled character to add to the game") }}
</h3> </h3>
<ul class="tokens"> <ul class="tokens">
<li v-for="role in fabled" :key="role.id" @click="setFabled(role)"> <li v-for="role in fabled" :key="role.id" @click="setFabled(role)">

View file

@ -4,7 +4,7 @@
v-if="modals.gameState" v-if="modals.gameState"
@close="toggleModal('gameState')" @close="toggleModal('gameState')"
> >
<h3>{{ $t("game-state-modal.current-game-state") }}</h3> <h3>{{ $t("Current Game State") }}</h3>
<textarea <textarea
:value="gamestate" :value="gamestate"
@input.stop="input = $event.target.value" @input.stop="input = $event.target.value"
@ -13,10 +13,10 @@
></textarea> ></textarea>
<div class="button-group"> <div class="button-group">
<div class="button townsfolk" @click="copy"> <div class="button townsfolk" @click="copy">
<font-awesome-icon icon="copy" /> {{ $t("game-state-modal.copy-json") }} <font-awesome-icon icon="copy" /> {{ $t("Copy JSON") }}
</div> </div>
<div class="button demon" @click="load" v-if="!session.isSpectator"> <div class="button demon" @click="load" v-if="!session.isSpectator">
<font-awesome-icon icon="cog" /> {{ $t("game-state-modal.load-state") }} <font-awesome-icon icon="cog" /> {{ $t("Load State") }}
</div> </div>
</div> </div>
</Modal> </Modal>

View file

@ -8,16 +8,16 @@
@click="toggleModal('reference')" @click="toggleModal('reference')"
icon="address-card" icon="address-card"
class="toggle" class="toggle"
:title="this.$t('show-reference')" :title="this.$t('Show Character Reference')"
/> />
<h3> <h3>
{{ $t("night-order-modal.night-order") }} {{ $t("Night Order") }}
<font-awesome-icon icon="cloud-moon" /> <font-awesome-icon icon="cloud-moon" />
{{ edition.name || "Custom Script" }} {{ edition.name || "Custom Script" }}
</h3> </h3>
<div class="night"> <div class="night">
<ul class="first"> <ul class="first">
<li class="headline">{{ $t("night-order-modal.first-night") }}</li> <li class="headline">{{ $t("First Night") }}</li>
<li <li
v-for="role in rolesFirstNight" v-for="role in rolesFirstNight"
:key="role.name" :key="role.name"
@ -56,7 +56,7 @@
</li> </li>
</ul> </ul>
<ul class="other"> <ul class="other">
<li class="headline">{{ $t("night-order-modal.other-nights") }}</li> <li class="headline">{{ $t("Other Nights") }}</li>
<li <li
v-for="role in rolesOtherNight" v-for="role in rolesOtherNight"
:key="role.name" :key="role.name"
@ -114,19 +114,23 @@ export default {
rolesFirstNight.push( rolesFirstNight.push(
{ {
id: "evil", id: "evil",
name: this.$t("night-order-modal.minion-info"), name: this.$t("Minion info"),
firstNight: 4, firstNight: 4,
team: "minion", team: "minion",
players: this.players.filter(p => p.role.team === "minion"), players: this.players.filter(p => p.role.team === "minion"),
firstNightReminder: this.$t("night-order-modal.minion-reminder") firstNightReminder: this.$t(
"• If more than one Minion, they all make eye contact with each other. • Show the “This is the Demon” card. Point to the Demon."
)
}, },
{ {
id: "evil", id: "evil",
name: this.$t("night-order-modal.demon-info"), name: this.$t("Demon info & bluffs"),
firstNight: 7, firstNight: 7,
team: "demon", team: "demon",
players: this.players.filter(p => p.role.team === "demon"), players: this.players.filter(p => p.role.team === "demon"),
firstNightReminder: this.$t("night-order-modal.demon-reminder") firstNightReminder: this.$t(
"• Show the “These are your minions” card. Point to each Minion. • Show the “These characters are not in play” card. Show 3 character tokens of good characters not in play."
)
} }
); );
} }

View file

@ -8,10 +8,10 @@
@click="toggleModal('nightOrder')" @click="toggleModal('nightOrder')"
icon="cloud-moon" icon="cloud-moon"
class="toggle" class="toggle"
:title="this.$t('show-night-order')" :title="this.$t('Show Night Order')"
/> />
<h3> <h3>
{{ $t("reference-modal.character-reference") }} {{ $t("Character Reference") }}
<font-awesome-icon icon="address-card" /> <font-awesome-icon icon="address-card" />
{{ edition.name || "Custom Script" }} {{ edition.name || "Custom Script" }}
</h3> </h3>
@ -53,7 +53,7 @@
<div class="team jinxed" v-if="jinxed.length"> <div class="team jinxed" v-if="jinxed.length">
<aside> <aside>
<h4>{{ $t("reference-modal.jinxed") }}</h4> <h4>{{ $t("Jinxed") }}</h4>
</aside> </aside>
<ul> <ul>
<li v-for="(jinx, index) in jinxed" :key="index"> <li v-for="(jinx, index) in jinxed" :key="index">

View file

@ -3,7 +3,7 @@
v-if="modals.reminder && availableReminders.length && players[playerIndex]" v-if="modals.reminder && availableReminders.length && players[playerIndex]"
@close="toggleModal('reminder')" @close="toggleModal('reminder')"
> >
<h3>{{ $t("reminder-token") }}:</h3> <h3>{{ $t("Choose a reminder token") }}:</h3>
<ul class="reminders"> <ul class="reminders">
<li <li
v-for="reminder in availableReminders" v-for="reminder in availableReminders"

View file

@ -2,11 +2,11 @@
<Modal v-if="modals.role && availableRoles.length" @close="close"> <Modal v-if="modals.role && availableRoles.length" @close="close">
<h3> <h3>
{{ {{
$t("role-modal.choose-character", { $t("Choose a new character for {player}", {
player: player:
playerIndex >= 0 && players.length playerIndex >= 0 && players.length
? players[playerIndex].name ? players[playerIndex].name
: $t("role-modal.bluffing") : $t("bluffing")
}) })
}} }}
</h3> </h3>
@ -39,14 +39,14 @@
:class="{ townsfolk: tab === 'editionRoles' }" :class="{ townsfolk: tab === 'editionRoles' }"
@click="tab = 'editionRoles'" @click="tab = 'editionRoles'"
> >
{{ $t("role-modal.edition-roles") }} {{ $t("Edition Roles") }}
</span> </span>
<span <span
class="button" class="button"
:class="{ townsfolk: tab === 'otherTravelers' }" :class="{ townsfolk: tab === 'otherTravelers' }"
@click="tab = 'otherTravelers'" @click="tab = 'otherTravelers'"
> >
{{ $t("role-modal.other-travelers") }} {{ $t("Other Travelers") }}
</span> </span>
</div> </div>
</Modal> </Modal>

View file

@ -4,7 +4,7 @@
v-if="modals.roles && nonTravelers >= 5" v-if="modals.roles && nonTravelers >= 5"
@close="toggleModal('roles')" @close="toggleModal('roles')"
> >
<i18n path="roles-modal.select-roles" tag="h3"> <i18n path="Select the characters for {nonTravelers} players:" tag="h3">
<template #nonTravelers> <template #nonTravelers>
{{ nonTravelers }} {{ nonTravelers }}
</template> </template>
@ -35,13 +35,17 @@
<div class="warning" v-if="hasSelectedSetupRoles"> <div class="warning" v-if="hasSelectedSetupRoles">
<font-awesome-icon icon="exclamation-triangle" /> <font-awesome-icon icon="exclamation-triangle" />
<span> <span>
{{ $t("roles-modal.warning-incorrect-setup") }} {{
$t(
"Warning: there are characters selected that modify the game setup! The randomizer does not account for these characters."
)
}}
</span> </span>
</div> </div>
<label class="multiple" :class="{ checked: allowMultiple }"> <label class="multiple" :class="{ checked: allowMultiple }">
<font-awesome-icon :icon="allowMultiple ? 'check-square' : 'square'" /> <font-awesome-icon :icon="allowMultiple ? 'check-square' : 'square'" />
<input type="checkbox" name="allow-multiple" v-model="allowMultiple" /> <input type="checkbox" name="allow-multiple" v-model="allowMultiple" />
{{ $t("roles-modal.allow-duplicate") }} {{ $t("Allow duplicate characters") }}
</label> </label>
<div class="button-group"> <div class="button-group">
<div <div
@ -52,7 +56,7 @@
}" }"
> >
<font-awesome-icon icon="people-arrows" /> <font-awesome-icon icon="people-arrows" />
<i18n path="roles-modal.assign-randomly" tag="span"> <i18n path="Assign {roles} characters randomly" tag="span">
<template #roles> <template #roles>
{{ selectedRoles }} {{ selectedRoles }}
</template> </template>
@ -60,7 +64,7 @@
</div> </div>
<div class="button" @click="selectRandomRoles"> <div class="button" @click="selectRandomRoles">
<font-awesome-icon icon="random" /> <font-awesome-icon icon="random" />
{{ $t("roles-modal.shuffle") }} {{ $t("Shuffle characters") }}
</div> </div>
</div> </div>
</Modal> </Modal>

View file

@ -12,7 +12,7 @@
v-if="session.isSpectator" v-if="session.isSpectator"
/> />
<h3>{{ $t("vote-history-modal.vote-history") }}</h3> <h3>{{ $t("Vote history") }}</h3>
<template v-if="!session.isSpectator"> <template v-if="!session.isSpectator">
<div class="options"> <div class="options">
@ -23,26 +23,26 @@
session.isVoteHistoryAllowed ? 'check-square' : 'square' session.isVoteHistoryAllowed ? 'check-square' : 'square'
]" ]"
/> />
{{ $t("vote-history-modal.accessible-players") }} {{ $t("Accessible to players") }}
</div> </div>
<div class="option" @click="clearVoteHistory"> <div class="option" @click="clearVoteHistory">
<font-awesome-icon icon="trash-alt" /> <font-awesome-icon icon="trash-alt" />
{{ $t("vote-history-modal.clear") }} {{ $t("Clear for everyone") }}
</div> </div>
</div> </div>
</template> </template>
<table> <table>
<thead> <thead>
<tr> <tr>
<td>{{ $t("vote-history-modal.time") }}</td> <td>{{ $t("Time") }}</td>
<td>{{ $t("vote-history-modal.nominator") }}</td> <td>{{ $t("Nominator") }}</td>
<td>{{ $t("vote-history-modal.nominee") }}</td> <td>{{ $t("Nominee") }}</td>
<td>{{ $t("vote-history-modal.type") }}</td> <td>{{ $t("Type") }}</td>
<td>{{ $t("vote-history-modal.votes") }}</td> <td>{{ $t("Votes") }}</td>
<td>{{ $t("vote-history-modal.majority") }}</td> <td>{{ $t("Majority") }}</td>
<td> <td>
<font-awesome-icon icon="user-friends" /> <font-awesome-icon icon="user-friends" />
{{ $t("vote-history-modal.voters") }} {{ $t("Voters") }}
</td> </td>
</tr> </tr>
</thead> </thead>

View file

@ -69,6 +69,7 @@ new Vue({
i18n: new VueI18n({ i18n: new VueI18n({
locale: navigator.language || navigator.userLanguage, locale: navigator.language || navigator.userLanguage,
fallbackLocale: "en-US", fallbackLocale: "en-US",
formatFallbackMessages: true,
messages: { "en-US": translations } messages: { "en-US": translations }
}), }),
store store