Open duoduo369 opened 3 years ago
For the front end, this is currently being worked on in #5502. For the card text and game log, that will be harder/not possible to implement.
The front-end translation infrastructure is now live, but we don't have any translations available.
I think there are three things to do:
So I am having an idea to help with translating the back-end generated content. It consists of relying on the translation key in the back-end and then resolve the keys when receiving the messages in the message handler in the front-end. The patch below shows how it would work for the messages in the lobby. I also started doing the same with the chat of the game board.
I know it is a naive implementation, I am new to Clojure :-).
Before I continue working on this, I wonder whether the dev team would accept/consider such approach.
diff --git a/src/cljs/nr/lobby.cljs b/src/cljs/nr/lobby.cljs
index a13ef4068..687618bed 100644
--- a/src/cljs/nr/lobby.cljs
+++ b/src/cljs/nr/lobby.cljs
@@ -1,6 +1,7 @@
(ns nr.lobby
(:require-macros [cljs.core.async.macros :refer [go]])
(:require
+ [clojure.string :refer [split starts-with? join]]
[cljs.core.async :refer [<!] :as async]
[clojure.set :refer [difference union]]
[nr.ajax :refer [GET]]
@@ -23,11 +24,36 @@
(defmethod ws/event-msg-handler :lobby/list [{data :?data}]
(swap! app-state assoc :games data))
+(defn from-system-user?
+ [msg]
+ (= "__system__" (:user msg)))
+
+(defn tr-inplace
+ [msg]
+ (join " "
+ (for [t (split msg " ")]
+ (if (and (starts-with? t ":") (< 1 (count t)))
+ (tr [(keyword (subs t 1))])
+ t))))
+
+(defn tr-messages
+ [messages]
+ (for [m messages]
+ (if (from-system-user? m)
+ (assoc m :text (tr-inplace (:text m)))
+ m)))
+
+(defn tr-data [data]
+ (if (contains? data :messages)
+ (update-in data [:messages] tr-messages)
+ data))
+
(defmethod ws/event-msg-handler :lobby/state [{data :?data}]
(when-not (= "local-replay" (:gameid @app-state))
- (swap! app-state assoc :current-game data)
- (when (:started data)
- (ws/ws-send! [:game/resync {:gameid (:gameid data)}]))))
+ (let [data (tr-data data)]
+ (swap! app-state assoc :current-game data)
+ (when (:started data)
+ (ws/ws-send! [:game/resync {:gameid (:gameid data)}])))))
(defmethod ws/event-msg-handler :lobby/notification [{data :?data}]
(play-sound data))
That doesn't provide enough information about how it would work or what it would look like.
I can work on a draft PR if that helps.
ok so my take here:
So for those situations, you can't use 1:1 translation. If we had translation strings, they would have to
1) be unique for almost every single card (there are a few cards generic enough to share strings)
2) involve passing maps of parameters to the system-msg function (everything will need to be written like string.format, ie :msg (msg (tr [:game.cards.generic.pay-or-take-tag] "forces the runner to pay [x] or take a tag" {:x "8 [credits]"}))
3) But since the numbers also need to be translated, you need to run translations on the parameters or compose types for that, so it might look more like: :msg (msg (tr [:game.cards.generic.pay-or-take-tag] "forces the runner to pay [x] or take a tag", {:x "[x1] [Credits]" :x1 8}))
, where system-msg needs to recursively parse the translation map (casting numbers into languages is easy enough)
Additionally, you can't compose translation strings in a linear way (ie (tr [does-one-thing]) (tr [to also]) (tr [do some other thing])
) because not all of the languages we support work like that.
But now we have a problem. We have 2000 cards to rewrite, there will be about 8000 translation strings that need to be done per language, and now everything is extremely hard to maintain or change. You also have to look up and maintain variable names with each translation.
Compare this to the UI translation, where the components don't change, aren't computed, and don't really have a maintenance burden.
I really would love this game to be just playable in any language, but I think this type of change wouldn't be maintainable in the long term, and wouldn't actually lead to proper translations in the short term because not only is the amount of work is comparable to translating a full novel, but it will need to be redone if we ever do any formatting changes with the way any of our systems work (as an example, I'm planning on extend :msg
to write forced costs as [player] pays [x] to satisfy [card]
- if that's pushed out to around 150 cards, that's 150 translations that are now non-functional.)
Thank you @NoahTheDuke for the feedback and @NBKelly for the very detailed explanation. I now understand the complexity behind this issue.
I really would love this game to be just playable in any language, but I think this type of change wouldn't be maintainable in the long term, and wouldn't actually lead to proper translations in the short term
I can only agree based on what you've said. I will focus on improving the translation of the front-end. I have a bunch of changes which will soon be ready for a PR.
I've made some progress on the front-end and raised the PR #7506. If the approach makes sense to you, I will continue reviewing the code and add translation support wherever missing.
p.s.: do we want to continue discussing i18n on this old issue or should we/I report a new one?
If project can provider I18N, like load .mo file for game board or something, we can translate po file for non-native man in community.
For now the text is write directly in code, it's hard for I18N.