Allow translating server messages

This commit is contained in:
Trevor Slocum 2024-03-18 11:48:59 -07:00
parent 3412f31250
commit 2858626600
6 changed files with 358 additions and 116 deletions

13
go.mod
View File

@ -7,9 +7,11 @@ require (
github.com/alexedwards/argon2id v1.0.0
github.com/gobwas/ws v1.3.2
github.com/gorilla/mux v1.8.1
github.com/jackc/pgx/v5 v5.5.3
github.com/jackc/pgx/v5 v5.5.5
github.com/jlouis/glicko2 v1.0.0
github.com/leonelquinteros/gotext v1.5.3-0.20231003122255-12a99145a351
github.com/matcornic/hermes/v2 v2.1.0
golang.org/x/text v0.14.0
)
require (
@ -17,7 +19,7 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
github.com/PuerkitoBio/goquery v1.8.1 // indirect
github.com/PuerkitoBio/goquery v1.9.1 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
@ -37,8 +39,7 @@ require (
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/vanng822/css v1.0.1 // indirect
github.com/vanng822/go-premailer v1.20.2 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.18.0 // indirect
)

30
go.sum
View File

@ -12,13 +12,12 @@ github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZC
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI=
github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY=
github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w=
github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw=
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
@ -51,8 +50,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s=
github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
@ -62,6 +61,10 @@ github.com/jlouis/glicko2 v1.0.0/go.mod h1:5dzlxjhVPPLk+wiUwwF2oVyDwsNXMgnw7WrLR
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leonelquinteros/gotext v1.5.2 h1:T2y6ebHli+rMBCjcJlHTXyUrgXqsKBhl/ormgvt7lPo=
github.com/leonelquinteros/gotext v1.5.2/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M=
github.com/leonelquinteros/gotext v1.5.3-0.20231003122255-12a99145a351 h1:Rk+RkO4xEZMkEok69CbeA6cgXKyVCsgF3qGGGR46pd8=
github.com/leonelquinteros/gotext v1.5.3-0.20231003122255-12a99145a351/go.mod h1:qQRISjoonXYFdRGrTG1LARQ38Gpibad0IPeB4hpvyyM=
github.com/matcornic/hermes/v2 v2.1.0 h1:9TDYFBPFv6mcXanaDmRDEp/RTWj0dTTi+LpFnnnfNWc=
github.com/matcornic/hermes/v2 v2.1.0/go.mod h1:2+ziJeoyRfaLiATIL8VZ7f9hpzH4oDHqTmn0bhrsgVI=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
@ -106,8 +109,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -117,14 +120,12 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
@ -134,7 +135,6 @@ golang.org/x/sys v0.0.0-20190225065934-cc5685c2db12/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -143,8 +143,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@ -153,8 +153,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=

View File

@ -71,6 +71,7 @@ type serverClient struct {
id int
json bool
name []byte
language string
account *account
accountID int
connected int64

View File

@ -0,0 +1,176 @@
msgid ""
msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"X-Generator: xgotext\n"
msgid "%s accepted double."
msgstr ""
msgid "%s declined double offer."
msgstr ""
msgid "%s offers a double (%d points)."
msgstr ""
msgid "Accepted double."
msgstr ""
msgid "Choose which doubles you want for your acey-deucey."
msgstr ""
msgid "Command ignored: You are spectating this match."
msgstr ""
msgid "Connect with other players and stay up to date on the latest changes. Visit %s"
msgstr ""
msgid "Created match: %s"
msgstr ""
msgid "Declined double offer"
msgstr ""
msgid "Double offered to opponent (%d points)."
msgstr ""
msgid "Failed to change password."
msgstr ""
msgid "Failed to change password: incorrect existing password."
msgstr ""
msgid "Failed to change password: you are logged in as a guest."
msgstr ""
msgid "Failed to create match: Please leave the match you are in before creating another."
msgstr ""
msgid "Failed to log in: %s"
msgstr ""
msgid "Illegal move."
msgstr ""
msgid "Invalid password."
msgstr ""
msgid "Invalid replay ID provided."
msgstr ""
msgid "Invalid username: must contain at least one non-numeric character."
msgstr ""
msgid "Invalid username: must contain only letters, numbers and underscores."
msgstr ""
msgid "It is not your turn to move."
msgstr ""
msgid "It is not your turn to roll."
msgstr ""
msgid "It is not your turn."
msgstr ""
msgid "Joined match: %s"
msgstr ""
msgid "Match not found."
msgstr ""
msgid "Message not sent: There is no one else in the match."
msgstr ""
msgid "Message not sent: You are not currently in a match."
msgstr ""
msgid "No account was found with the provided username and password. To log in as a guest, do not enter a password."
msgstr ""
msgid "No replay was recorded for that game."
msgstr ""
msgid "Password changed successfully."
msgstr ""
msgid "Please leave the match you are in before joining another."
msgstr ""
msgid "Rejoined match: %s"
msgstr ""
msgid "Rematch offer sent."
msgstr ""
msgid "Server error"
msgstr ""
msgid "That username is already in use."
msgstr ""
msgid "The following legal moves are available: %s"
msgstr ""
msgid "The match you are in is still in progress."
msgstr ""
msgid "Unknown command: %s"
msgstr ""
msgid "Waiting for response from opponent."
msgstr ""
msgid "You are not allowed to use that command."
msgstr ""
msgid "You are not currently in a match."
msgstr ""
msgid "You are spectating this match. Chat messages are not relayed."
msgstr ""
msgid "You do not currently hold the doubling cube."
msgstr ""
msgid "You have already requested a rematch."
msgstr ""
msgid "You may not accept the double until your opponent rejoins the match."
msgstr ""
msgid "You may not double at this time."
msgstr ""
msgid "You may not double until your opponent rejoins the match."
msgstr ""
msgid "You may not move until your opponent rejoins the match."
msgstr ""
msgid "You may not resign at this time."
msgstr ""
msgid "You may not resign until your opponent rejoins the match."
msgstr ""
msgid "You may not roll until your opponent rejoins the match."
msgstr ""
msgid "You must login before using other commands."
msgstr ""
msgid "You must roll first."
msgstr ""
msgid "You must wait until your opponent rejoins the match before continuing the game."
msgstr ""
msgid "Your opponent left the match."
msgstr ""
msgid "Your opponent would like to play again. Type %s to accept."
msgstr ""

View File

@ -1,9 +1,12 @@
package server
//go:generate xgotext -no-locations -default bgammon -in . -out locales
import (
"bufio"
"bytes"
"crypto/rand"
"embed"
"encoding/base64"
"fmt"
"log"
@ -19,6 +22,8 @@ import (
"time"
"code.rocket9labs.com/tslocum/bgammon"
"github.com/leonelquinteros/gotext"
"golang.org/x/text/language"
)
const clientTimeout = 40 * time.Second
@ -33,6 +38,15 @@ var (
alphaNumericUnderscore = regexp.MustCompile(`^[A-Za-z0-9_]+$`)
)
//go:embed locales
var assetFS embed.FS
var englishIdentifier = []byte("en")
func init() {
gotext.SetDomain("bgammon-en")
}
type serverCommand struct {
client *serverClient
command []byte
@ -66,7 +80,9 @@ type server struct {
passwordSalt string
resetSalt string
tz *time.Location
tz *time.Location
languageTags []language.Tag
languageNames [][]byte
relayChat bool // Chats are not relayed normally. This option is only used by local servers.
verbose bool
@ -85,6 +101,7 @@ func NewServer(tz string, dataSource string, mailServer string, passwordSalt str
relayChat: relayChat,
verbose: verbose,
}
s.loadLocales()
if tz != "" {
var err error
@ -114,25 +131,6 @@ func NewServer(tz string, dataSource string, mailServer string, passwordSalt str
allowDebugCommands = allowDebug
/*gm := bgammon.NewGame(bgammon.VariantBackgammon)
gm.Turn = 1
gm.Roll1 = 2
gm.Roll2 = 3
log.Println(gm.MayBearOff(1, false))
gm.Player1.Entered = true
gm.Player2.Entered = true
log.Println(gm.Board)
//ok, expanded := gm.AddMoves([][]int8{{3, 1}}, false)
//log.Println(ok, expanded, "!")
log.Println(gm.MayBearOff(1, false))
gs := &bgammon.GameState{
Game: gm,
PlayerNumber: 1,
Available: gm.LegalMoves(false),
}
log.Printf("%+v", gs)
os.Exit(0)*/
go s.handleNewGameIDs()
go s.handleNewClientIDs()
go s.handleCommands()
@ -140,6 +138,57 @@ func NewServer(tz string, dataSource string, mailServer string, passwordSalt str
return s
}
func (s *server) loadLocales() {
entries, err := assetFS.ReadDir("locales")
if err != nil {
log.Fatalf("failed to list files in locales directory: %s", err)
}
var availableTags = []language.Tag{
language.MustParse("en_US"),
}
var availableNames = [][]byte{
[]byte("en"),
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
availableTags = append(availableTags, language.MustParse(entry.Name()))
availableNames = append(availableNames, []byte(entry.Name()))
b, err := assetFS.ReadFile(fmt.Sprintf("locales/%s/%s.po", entry.Name(), entry.Name()))
if err != nil {
log.Fatalf("failed to read locale %s: %s", entry.Name(), err)
}
po := gotext.NewPo()
po.Parse(b)
gotext.GetStorage().AddTranslator(fmt.Sprintf("bgammon-%s", entry.Name()), po)
}
s.languageTags = availableTags
s.languageNames = availableNames
}
func (s *server) matchLanguage(identifier []byte) []byte {
if len(identifier) == 0 {
return englishIdentifier
}
tag, err := language.Parse(string(identifier))
if err != nil {
return englishIdentifier
}
var preferred = []language.Tag{tag}
useLanguage, index, _ := language.NewMatcher(s.languageTags).Match(preferred...)
useLanguageCode := useLanguage.String()
if index < 0 || useLanguageCode == "" || strings.HasPrefix(useLanguageCode, "en") {
return englishIdentifier
}
return s.languageNames[index]
}
func (s *server) ListenLocal() chan net.Conn {
conns := make(chan net.Conn)
go s.handleLocal(conns)
@ -194,6 +243,7 @@ func (s *server) handleWebSocket(w http.ResponseWriter, r *http.Request) {
c := &serverClient{
id: <-s.newClientIDs,
language: "bgammon-en",
accountID: -1,
connected: now,
active: now,
@ -341,6 +391,7 @@ func (s *server) handleConnection(conn net.Conn) {
c := &serverClient{
id: <-s.newClientIDs,
language: "bgammon-en",
accountID: -1,
connected: now,
active: now,

View File

@ -9,6 +9,7 @@ import (
"time"
"code.rocket9labs.com/tslocum/bgammon"
"github.com/leonelquinteros/gotext"
)
func (s *server) handleCommands() {
@ -77,6 +78,10 @@ COMMANDS:
sendUsage()
continue
}
slashIndex := bytes.IndexRune(params[0], '/')
if slashIndex != -1 {
cmd.client.language = "bgammon-" + string(s.matchLanguage(params[0][slashIndex+1:]))
}
email = params[1]
username = params[2]
password = bytes.Join(params[3:], []byte("_"))
@ -109,8 +114,14 @@ COMMANDS:
readUsername := func() bool {
if cmd.client.json {
if len(params) > 1 {
username = params[1]
if len(params) > 0 {
slashIndex := bytes.IndexRune(params[0], '/')
if slashIndex != -1 {
cmd.client.language = "bgammon-" + string(s.matchLanguage(params[0][slashIndex+1:]))
}
if len(params) > 1 {
username = params[1]
}
}
} else {
if len(params) > 0 {
@ -121,14 +132,16 @@ COMMANDS:
username = s.randomUsername()
randomUsername = true
} else if !alphaNumericUnderscore.Match(username) {
cmd.client.Terminate("Invalid username: must contain only letters, numbers and underscores.")
cmd.client.Terminate(gotext.GetD(cmd.client.language, "Invalid username: must contain only letters, numbers and underscores."))
return false
}
if onlyNumbers.Match(username) {
cmd.client.Terminate("Invalid username: must contain at least one non-numeric character.")
log.Println(cmd.client.language)
log.Println("!")
cmd.client.Terminate(gotext.GetD(cmd.client.language, "Invalid username: must contain at least one non-numeric character."))
return false
} else if s.clientByUsername(username) != nil || s.clientByUsername(append([]byte("Guest_"), username...)) != nil || (!randomUsername && !s.nameAllowed(username)) {
cmd.client.Terminate("That username is already in use.")
cmd.client.Terminate(gotext.GetD(cmd.client.language, "That username is already in use."))
return false
}
return true
@ -147,10 +160,10 @@ COMMANDS:
if len(password) > 0 {
a, err := loginAccount(s.passwordSalt, username, password)
if err != nil {
cmd.client.Terminate(fmt.Sprintf("Failed to log in: %s", err))
cmd.client.Terminate(fmt.Sprintf(gotext.GetD(cmd.client.language, "Failed to log in: %s"), err))
continue
} else if a == nil {
cmd.client.Terminate("No account was found with the provided username and password. To log in as a guest, do not enter a password.")
cmd.client.Terminate(gotext.GetD(cmd.client.language, "No account was found with the provided username and password. To log in as a guest, do not enter a password."))
continue
}
@ -161,7 +174,7 @@ COMMANDS:
name = a.username
}
if s.clientByUsername(name) != nil {
cmd.client.Terminate("That username is already in use.")
cmd.client.Terminate(gotext.GetD(cmd.client.language, "That username is already in use."))
continue
}
@ -201,7 +214,7 @@ COMMANDS:
}
// Send message of the day.
cmd.client.sendNotice("Connect with other players and stay up to date on the latest changes. Visit bgammon.org/community")
cmd.client.sendNotice(fmt.Sprintf(gotext.GetD(cmd.client.language, "Connect with other players and stay up to date on the latest changes. Visit %s"), "bgammon.org/community"))
// Rejoin match in progress.
s.gamesLock.RLock()
@ -218,14 +231,14 @@ COMMANDS:
}
if rejoin {
g.addClient(cmd.client)
cmd.client.sendNotice(fmt.Sprintf("Rejoined match: %s", g.name))
cmd.client.sendNotice(fmt.Sprintf(gotext.GetD(cmd.client.language, "Rejoined match: %s"), g.name))
}
}
s.gamesLock.RUnlock()
continue
}
cmd.client.Terminate("You must login before using other commands.")
cmd.client.Terminate(gotext.GetD(cmd.client.language, "You must login before using other commands."))
continue
}
@ -235,7 +248,7 @@ COMMANDS:
case bgammon.CommandHelp, "h", bgammon.CommandJSON, bgammon.CommandList, "ls", bgammon.CommandBoard, "b", bgammon.CommandLeave, "l", bgammon.CommandReplay, bgammon.CommandSet, bgammon.CommandDisconnect, bgammon.CommandPong:
// These commands are allowed to be used by spectators.
default:
cmd.client.sendNotice("Command ignored: You are spectating this match.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Command ignored: You are spectating this match."))
continue
}
}
@ -271,12 +284,12 @@ COMMANDS:
continue
}
if clientGame == nil {
cmd.client.sendNotice("Message not sent: You are not currently in a match.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Message not sent: You are not currently in a match."))
continue
}
opponent := clientGame.opponent(cmd.client)
if opponent == nil {
cmd.client.sendNotice("Message not sent: There is no one else in the match.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Message not sent: There is no one else in the match."))
continue
}
ev := &bgammon.EventSay{
@ -305,7 +318,7 @@ COMMANDS:
cmd.client.sendEvent(ev)
case bgammon.CommandCreate, "c":
if clientGame != nil {
cmd.client.sendNotice("Failed to create match: Please leave the match you are in before creating another.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Failed to create match: Please leave the match you are in before creating another."))
continue
}
@ -387,7 +400,7 @@ COMMANDS:
s.games = append(s.games, g)
s.gamesLock.Unlock()
cmd.client.sendNotice(fmt.Sprintf("Created match: %s", g.name))
cmd.client.sendNotice(fmt.Sprintf(gotext.GetD(cmd.client.language, "Created match: %s"), g.name))
if len(g.password) == 0 {
cmd.client.sendNotice("Note: Please be patient as you wait for another player to join the match. A chime will sound when another player joins. While you wait, join the bgammon.org community via Discord, Matrix or IRC at bgammon.org/community")
@ -395,7 +408,7 @@ COMMANDS:
case bgammon.CommandJoin, "j":
if clientGame != nil {
cmd.client.sendEvent(&bgammon.EventFailedJoin{
Reason: "Please leave the match you are in before joining another.",
Reason: gotext.GetD(cmd.client.language, "Please leave the match you are in before joining another."),
})
continue
}
@ -436,7 +449,7 @@ COMMANDS:
if joinGameID == 0 {
cmd.client.sendEvent(&bgammon.EventFailedJoin{
Reason: "Match not found.",
Reason: gotext.GetD(cmd.client.language, "Match not found."),
})
continue
}
@ -451,7 +464,7 @@ COMMANDS:
providedPassword := bytes.ReplaceAll(bytes.Join(params[1:], []byte(" ")), []byte("_"), []byte(" "))
if len(g.password) != 0 && (len(params) < 2 || !bytes.Equal(g.password, providedPassword)) {
cmd.client.sendEvent(&bgammon.EventFailedJoin{
Reason: "Invalid password.",
Reason: gotext.GetD(cmd.client.language, "Invalid password."),
})
s.gamesLock.Unlock()
continue COMMANDS
@ -466,9 +479,9 @@ COMMANDS:
spectator := g.addClient(cmd.client)
s.gamesLock.Unlock()
cmd.client.sendNotice(fmt.Sprintf("Joined match: %s", g.name))
cmd.client.sendNotice(fmt.Sprintf(gotext.GetD(cmd.client.language, "Joined match: %s"), g.name))
if spectator {
cmd.client.sendNotice("You are spectating this match. Chat messages are not relayed.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You are spectating this match. Chat messages are not relayed."))
}
continue COMMANDS
}
@ -476,12 +489,12 @@ COMMANDS:
s.gamesLock.Unlock()
cmd.client.sendEvent(&bgammon.EventFailedJoin{
Reason: "Match not found.",
Reason: gotext.GetD(cmd.client.language, "Match not found."),
})
case bgammon.CommandLeave, "l":
if clientGame == nil {
cmd.client.sendEvent(&bgammon.EventFailedLeave{
Reason: "You are not currently in a match.",
Reason: gotext.GetD(cmd.client.language, "You are not currently in a match."),
})
continue
}
@ -495,14 +508,14 @@ COMMANDS:
clientGame.removeClient(cmd.client)
case bgammon.CommandDouble, "d":
if clientGame == nil {
cmd.client.sendNotice("You are not currently in a match.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You are not currently in a match."))
continue
} else if clientGame.Winner != 0 {
continue
}
if clientGame.Turn != cmd.client.playerNumber {
cmd.client.sendNotice("It is not your turn.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "It is not your turn."))
continue
}
@ -512,26 +525,26 @@ COMMANDS:
Available: clientGame.LegalMoves(false),
}
if !gameState.MayDouble() {
cmd.client.sendNotice("You may not double at this time.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You may not double at this time."))
continue
}
if clientGame.DoublePlayer != 0 && clientGame.DoublePlayer != cmd.client.playerNumber {
cmd.client.sendNotice("You do not currently hold the doubling cube.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You do not currently hold the doubling cube."))
continue
}
opponent := clientGame.opponent(cmd.client)
if opponent == nil {
cmd.client.sendNotice("You may not double until your opponent rejoins the match.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You may not double until your opponent rejoins the match."))
continue
}
clientGame.DoubleOffered = true
clientGame.NextPartialTurn(opponent.playerNumber)
cmd.client.sendNotice(fmt.Sprintf("Double offered to opponent (%d points).", clientGame.DoubleValue*2))
clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf("%s offers a double (%d points).", cmd.client.name, clientGame.DoubleValue*2))
cmd.client.sendNotice(fmt.Sprintf(gotext.GetD(cmd.client.language, "Double offered to opponent (%d points)."), clientGame.DoubleValue*2))
clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf(gotext.GetD(clientGame.opponent(cmd.client).language, "%s offers a double (%d points)."), cmd.client.name, clientGame.DoubleValue*2))
clientGame.eachClient(func(client *serverClient) {
if client.json {
@ -540,7 +553,7 @@ COMMANDS:
})
case bgammon.CommandResign:
if clientGame == nil {
cmd.client.sendNotice("You are not currently in a match.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You are not currently in a match."))
continue
} else if clientGame.Winner != 0 {
continue
@ -552,20 +565,20 @@ COMMANDS:
Available: clientGame.LegalMoves(false),
}
if !gameState.MayResign() {
cmd.client.sendNotice("You may not resign at this time.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You may not resign at this time."))
continue
}
opponent := clientGame.opponent(cmd.client)
if opponent == nil {
cmd.client.sendNotice("You may not resign until your opponent rejoins the match.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You may not resign until your opponent rejoins the match."))
continue
}
clientGame.NextPartialTurn(opponent.playerNumber)
cmd.client.sendNotice("Declined double offer")
clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf("%s declined double offer.", cmd.client.name))
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Declined double offer"))
clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf(gotext.GetD(clientGame.opponent(cmd.client).language, "%s declined double offer."), cmd.client.name))
clientGame.replay = append([][]byte{[]byte(fmt.Sprintf("i %d %s %s %d %d %d %d %d %d", clientGame.Started.Unix(), clientGame.Player1.Name, clientGame.Player2.Name, clientGame.Points, clientGame.Player1.Points, clientGame.Player2.Points, clientGame.Winner, clientGame.DoubleValue, clientGame.Variant))}, clientGame.replay...)
@ -628,7 +641,7 @@ COMMANDS:
case bgammon.CommandRoll, "r":
if clientGame == nil {
cmd.client.sendEvent(&bgammon.EventFailedRoll{
Reason: "You are not currently in a match.",
Reason: gotext.GetD(cmd.client.language, "You are not currently in a match."),
})
continue
} else if clientGame.Winner != 0 {
@ -638,14 +651,14 @@ COMMANDS:
opponent := clientGame.opponent(cmd.client)
if opponent == nil {
cmd.client.sendEvent(&bgammon.EventFailedRoll{
Reason: "You may not roll until your opponent rejoins the match.",
Reason: gotext.GetD(cmd.client.language, "You may not roll until your opponent rejoins the match."),
})
continue
}
if !clientGame.roll(cmd.client.playerNumber) {
cmd.client.sendEvent(&bgammon.EventFailedRoll{
Reason: "It is not your turn to roll.",
Reason: gotext.GetD(cmd.client.language, "It is not your turn to roll."),
})
continue
}
@ -766,7 +779,7 @@ COMMANDS:
case bgammon.CommandMove, "m", "mv":
if clientGame == nil {
cmd.client.sendEvent(&bgammon.EventFailedMove{
Reason: "You are not currently in a match.",
Reason: gotext.GetD(cmd.client.language, "You are not currently in a match."),
})
continue
} else if clientGame.Winner != 0 {
@ -776,7 +789,7 @@ COMMANDS:
if clientGame.Turn != cmd.client.playerNumber {
cmd.client.sendEvent(&bgammon.EventFailedMove{
Reason: "It is not your turn to move.",
Reason: gotext.GetD(cmd.client.language, "It is not your turn to move."),
})
continue
}
@ -784,7 +797,7 @@ COMMANDS:
opponent := clientGame.opponent(cmd.client)
if opponent == nil {
cmd.client.sendEvent(&bgammon.EventFailedMove{
Reason: "You may not move until your opponent rejoins the match.",
Reason: gotext.GetD(cmd.client.language, "You may not move until your opponent rejoins the match."),
})
continue
}
@ -821,7 +834,7 @@ COMMANDS:
cmd.client.sendEvent(&bgammon.EventFailedMove{
From: from,
To: to,
Reason: "Illegal move.",
Reason: gotext.GetD(cmd.client.language, "Illegal move."),
})
continue COMMANDS
}
@ -835,7 +848,7 @@ COMMANDS:
cmd.client.sendEvent(&bgammon.EventFailedMove{
From: 0,
To: 0,
Reason: "Illegal move.",
Reason: gotext.GetD(cmd.client.language, "Illegal move."),
})
continue
}
@ -853,14 +866,14 @@ COMMANDS:
clientGame.handleWin()
case bgammon.CommandReset:
if clientGame == nil {
cmd.client.sendNotice("You are not currently in a match.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You are not currently in a match."))
continue
} else if clientGame.Winner != 0 {
continue
}
if clientGame.Turn != cmd.client.playerNumber {
cmd.client.sendNotice("It is not your turn.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "It is not your turn."))
continue
}
@ -889,7 +902,7 @@ COMMANDS:
}
case bgammon.CommandOk, "k":
if clientGame == nil {
cmd.client.sendNotice("You are not currently in a match.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You are not currently in a match."))
continue
} else if clientGame.Winner != 0 {
continue
@ -897,7 +910,7 @@ COMMANDS:
opponent := clientGame.opponent(cmd.client)
if opponent == nil {
cmd.client.sendNotice("You must wait until your opponent rejoins the match before continuing the game.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You must wait until your opponent rejoins the match before continuing the game."))
continue
}
@ -905,7 +918,7 @@ COMMANDS:
if clientGame.Turn != cmd.client.playerNumber {
opponent := clientGame.opponent(cmd.client)
if opponent == nil {
cmd.client.sendNotice("You may not accept the double until your opponent rejoins the match.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You may not accept the double until your opponent rejoins the match."))
continue
}
@ -914,24 +927,24 @@ COMMANDS:
clientGame.DoublePlayer = cmd.client.playerNumber
clientGame.NextPartialTurn(opponent.playerNumber)
cmd.client.sendNotice("Accepted double.")
opponent.sendNotice(fmt.Sprintf("%s accepted double.", cmd.client.name))
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Accepted double."))
opponent.sendNotice(fmt.Sprintf(gotext.GetD(opponent.language, "%s accepted double."), cmd.client.name))
clientGame.replay = append(clientGame.replay, []byte(fmt.Sprintf("%d d %d 1", clientGame.Turn, clientGame.DoubleValue)))
clientGame.eachClient(func(client *serverClient) {
clientGame.sendBoard(client, false)
})
} else {
cmd.client.sendNotice("Waiting for response from opponent.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Waiting for response from opponent."))
}
continue
} else if clientGame.Turn != cmd.client.playerNumber {
cmd.client.sendNotice("It is not your turn.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "It is not your turn."))
continue
}
if clientGame.Roll1 == 0 || clientGame.Roll2 == 0 {
cmd.client.sendNotice("You must roll first.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You must roll first."))
continue
}
@ -940,7 +953,7 @@ COMMANDS:
available := bgammon.FlipMoves(legalMoves, cmd.client.playerNumber, clientGame.Variant)
bgammon.SortMoves(available)
cmd.client.sendEvent(&bgammon.EventFailedOk{
Reason: fmt.Sprintf("The following legal moves are available: %s", bgammon.FormatMoves(available)),
Reason: fmt.Sprintf(gotext.GetD(cmd.client.language, "The following legal moves are available: %s"), bgammon.FormatMoves(available)),
})
continue
}
@ -952,7 +965,7 @@ COMMANDS:
}
if doubles < 1 || doubles > 6 {
cmd.client.sendEvent(&bgammon.EventFailedOk{
Reason: "Choose which doubles you want for your acey-deucey.",
Reason: gotext.GetD(cmd.client.language, "Choose which doubles you want for your acey-deucey."),
})
continue
}
@ -977,8 +990,8 @@ COMMANDS:
clientGame.nextTurn(true)
clientGame.Roll1, clientGame.Roll2 = 0, 0
if !clientGame.roll(cmd.client.playerNumber) {
cmd.client.Terminate("Server error")
opponent.Terminate("Server error")
cmd.client.Terminate(gotext.GetD(cmd.client.language, "Server error"))
opponent.Terminate(gotext.GetD(opponent.language, "Server error"))
continue
}
clientGame.Reroll = false
@ -998,16 +1011,16 @@ COMMANDS:
}
case bgammon.CommandRematch, "rm":
if clientGame == nil {
cmd.client.sendNotice("You are not currently in a match.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You are not currently in a match."))
continue
} else if clientGame.Winner == 0 {
cmd.client.sendNotice("The match you are in is still in progress.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "The match you are in is still in progress."))
continue
} else if clientGame.rematch == cmd.client.playerNumber {
cmd.client.sendNotice("You have already requested a rematch.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You have already requested a rematch."))
continue
} else if clientGame.client1 == nil || clientGame.client2 == nil {
cmd.client.sendNotice("Your opponent left the match.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Your opponent left the match."))
continue
} else if clientGame.rematch != 0 && clientGame.rematch != cmd.client.playerNumber {
s.gamesLock.Lock()
@ -1072,20 +1085,20 @@ COMMANDS:
} else {
clientGame.rematch = cmd.client.playerNumber
clientGame.opponent(cmd.client).sendNotice("Your opponent would like to play again. Type /rematch to accept.")
cmd.client.sendNotice("Rematch offer sent.")
clientGame.opponent(cmd.client).sendNotice(fmt.Sprintf(gotext.GetD(clientGame.opponent(cmd.client).language, "Your opponent would like to play again. Type %s to accept."), "/rematch"))
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Rematch offer sent."))
continue
}
case bgammon.CommandBoard, "b":
if clientGame == nil {
cmd.client.sendNotice("You are not currently in a match.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You are not currently in a match."))
continue
}
clientGame.sendBoard(cmd.client, false)
case bgammon.CommandPassword:
if cmd.client.account == nil {
cmd.client.sendNotice("Failed to change password: you are logged in as a guest.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Failed to change password: you are logged in as a guest."))
continue
} else if len(params) < 2 {
cmd.client.sendNotice("Please specify your old and new passwords as follows: password <old> <new>")
@ -1094,16 +1107,16 @@ COMMANDS:
a, err := loginAccount(s.passwordSalt, cmd.client.name, params[0])
if err != nil || a == nil || a.id == 0 {
cmd.client.sendNotice("Failed to change password: incorrect existing password.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Failed to change password: incorrect existing password."))
continue
}
err = setAccountPassword(s.passwordSalt, a.id, string(bytes.Join(params[1:], []byte("_"))))
if err != nil {
cmd.client.sendNotice("Failed to change password.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Failed to change password."))
continue
}
cmd.client.sendNotice("Password changed successfully.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Password changed successfully."))
case bgammon.CommandSet:
if len(params) < 2 {
cmd.client.sendNotice("Please specify the setting name and value as follows: set <name> <value>")
@ -1154,17 +1167,17 @@ COMMANDS:
} else {
id, err = strconv.Atoi(string(params[0]))
if err != nil || id < 0 {
cmd.client.sendNotice("Invalid replay ID provided.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Invalid replay ID provided."))
continue
}
replay, err = replayByID(id)
if err != nil {
cmd.client.sendNotice("Invalid replay ID provided.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Invalid replay ID provided."))
continue
}
}
if len(replay) == 0 {
cmd.client.sendNotice("No replay was recorded for that game.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "No replay was recorded for that game."))
continue
}
cmd.client.sendEvent(&bgammon.EventReplay{
@ -1188,7 +1201,7 @@ COMMANDS:
matches, err := matchHistory(string(params[0]))
if err != nil {
cmd.client.sendNotice("Invalid replay ID provided.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "Invalid replay ID provided."))
continue
}
@ -1229,12 +1242,12 @@ COMMANDS:
// Do nothing.
case "endgame":
if !allowDebugCommands {
cmd.client.sendNotice("You are not allowed to use that command.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You are not allowed to use that command."))
continue
}
if clientGame == nil {
cmd.client.sendNotice("You are not currently in a match.")
cmd.client.sendNotice(gotext.GetD(cmd.client.language, "You are not currently in a match."))
continue
}
@ -1254,7 +1267,7 @@ COMMANDS:
})
default:
log.Printf("Received unknown command from client %s: %s", cmd.client.label(), cmd.command)
cmd.client.sendNotice(fmt.Sprintf("Unknown command: %s", cmd.command))
cmd.client.sendNotice(fmt.Sprintf(gotext.GetD(cmd.client.language, "Unknown command: %s"), cmd.command))
}
}
}