├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── index.html ├── languages.json ├── multilang.js ├── readme.md └── style.css /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | 19 | $ cat .gitattributes 20 | index.html linguist-documentation 21 | style.css linguist-documentation -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://www.buymeacoffee.com/bdr76'] 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | JavaScript multi-language localisation example 6 | 7 | 8 | 9 | 67 | 68 | Select language from list to switch between translations: 69 |
71 | 72 |
73 |
Start game
74 |
Sound
75 |
Editor
76 |
Music
77 |
Options
78 |

79 |
80 | Reset game
81 |
no
82 |
yes
83 |

84 |
85 | Credits1 86 | Harry
87 | Credits2 88 | Dick
89 | Credits3 90 | Tom
91 |

92 |
End text
93 | 94 | 95 | -------------------------------------------------------------------------------- /languages.json: -------------------------------------------------------------------------------- 1 | { 2 | "en": { 3 | "langdesc": "English", 4 | "Start": "Start game", 5 | "Editor": "Editor", 6 | "Options": "Options", 7 | "Sound": "Sound", 8 | "Music": "Music", 9 | "Reset game": "This will reset your game progress. Are you sure?", 10 | "Yes": "Yes", 11 | "No": "No", 12 | "Credits1": "Program by", 13 | "Credits2": "Artwork and graphics by", 14 | "Credits3": "Special thanks to", 15 | "EndText": "Congratulations! Thank you for playing!" 16 | }, 17 | "de": { 18 | "langdesc": "German", 19 | "Start": "Spiel starten", 20 | "Editor": "Editor", 21 | "Options": "Optionen", 22 | "Sound": "Geräusche", 23 | "Music": "Musik", 24 | "Reset game": "Möchten Sie Ihren Fortschritt wirklich zurücksetzen?", 25 | "Yes": "Ja", 26 | "No": "Nein", 27 | "Credits1": "Programm von", 28 | "Credits2": "Artwork und Grafiken von", 29 | "Credits3": "Mit Dank an", 30 | "EndText": "Gratuliere! Danke für's Spielen!" 31 | }, 32 | "fr": { 33 | "langdesc": "French", 34 | "Start": "Jouer", 35 | "Editor": "Éditer", 36 | "Options": "Paramètres", 37 | "Sound": "Sons", 38 | "Music": "Musique", 39 | "Reset game": "Voulez-vous vraiment réinitialiser votre progression?", 40 | "Yes": "Oui", 41 | "No": "Non", 42 | "Credits1": "Programme de", 43 | "Credits2": "Illustrations et graphiques de", 44 | "Credits3": "Remerciements à", 45 | "EndText": "Félicitations! Merci d'avoir joué le game!" 46 | }, 47 | "es": { 48 | "langdesc": "Spanish", 49 | "Start": "Comenzar", 50 | "Editor": "Editor", 51 | "Options": "Opciones", 52 | "Sound": "Sonido", 53 | "Music": "Música", 54 | "Reset game": "Reiniciar todos los puzzles resueltos. ¿Estás seguro?", 55 | "Yes": "Sí", 56 | "No": "No", 57 | "Credits1": "Programa por", 58 | "Credits2": "Ilustraciones y gráficos por", 59 | "Credits3": "Con agradecimiento", 60 | "EndText": "¡Felicidades! ¡Gracias por jugar!" 61 | }, 62 | "nl": { 63 | "langdesc": "Dutch", 64 | "Start": "Start spel", 65 | "Editor": "Bewerken", 66 | "Options": "Opties", 67 | "Sound": "Geluid", 68 | "Music": "Muziek", 69 | "Reset game": "Hiermee wis je alle spelvoortgang. Weet je het zeker?", 70 | "Yes": "Ja", 71 | "No": "Nee", 72 | "Credits1": "Programma door", 73 | "Credits2": "Tekeningen en illustraties door", 74 | "Credits3": "Met dank aan", 75 | "EndText": "Gefeliciteerd! Bedankt voor het spelen!" 76 | }, 77 | "ru": { 78 | "langdesc": "Russian", 79 | "Start": "Начать игру", 80 | "Editor": "Редактор", 81 | "Options": "Опции", 82 | "Sound": "звук", 83 | "Music": "Музыка", 84 | "Reset game": "Это приведет к сбросу ваш прогресс. Ты уверен?", 85 | "Yes": "да", 86 | "No": "Нет", 87 | "Credits1": "Запрограммированный", 88 | "Credits2": "Иллюстрации", 89 | "Credits3": "Особая благодарность", 90 | "EndText": "Поздравления! Спасибо за игру!" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /multilang.js: -------------------------------------------------------------------------------- 1 | // MultiLang - BdR 2016 2 | // JavaScript object to handle multilanguage, load phrases from JSON etc. 3 | 4 | var MultiLang = function(url, lang, onload) 5 | { 6 | // variables 7 | this.phrases = {}; 8 | 9 | // language code from parameter or if null then default to browser language preference 10 | // Keep only first two characters, for example 'en-US' -> 'en', or 'nl-NL' -> 'nl' etc. 11 | this.selectedLanguage = (lang || navigator.language || navigator.userLanguage).substring(0, 2); 12 | 13 | // onLoad callback function, call after loading JSON 14 | this.onLoad = onload; 15 | 16 | // load json from url 17 | if (typeof url !== 'undefined') { 18 | var obj = this; 19 | var req = new XMLHttpRequest(); 20 | 21 | // NOTE: will load asynchronously! 22 | req.open("GET", url, true); 23 | //req.setRequestHeader("User-Agent", navigator.userAgent); 24 | req.onreadystatechange = function (evt) { 25 | if (evt.target.readyState == 4 && evt.target.status == 200) // status == 200, do not allow "Cross origin requests" 26 | //if (evt.target.readyState == 4)// TESTING allow "Cross origin requests" to load from local harddisk 27 | { 28 | // load translations 29 | this.phrases = JSON.parse( evt.target.responseText ); 30 | 31 | // verify that the currently selected language exists in the translations 32 | this.setLanguage(this.selectedLanguage); 33 | 34 | // do callback when loading JSON is ready 35 | if (this.onLoad) { 36 | this.onLoad(); 37 | } 38 | 39 | }; 40 | }.bind(obj); // NOTE: bind onreadyfunction to MultiLang instead of XMLHttpRequest, so MultiLang.phrases will be set instead of added to XMLHttpRequest 41 | req.addEventListener("error", function(e) { 42 | console.log('MultiLang.js: Error reading json file.'); 43 | }, false); 44 | 45 | req.send(null); 46 | }; 47 | 48 | this.setLanguage = function(langcode) { 49 | 50 | // check if language code does not exist in available translations in json file 51 | // For example, available translated texts in json are 'en' and 'fr', but client language is 'es' 52 | if (!this.phrases.hasOwnProperty(langcode)) { 53 | // doesn't exist so default to the first available language, i.e. the top-most language in json file 54 | 55 | // NOTE: the order of properties in a JSON object are not *guaranteed* to be the same as loading time, 56 | // however in practice all browsers do return them in order 57 | for (var key in this.phrases) { 58 | if (this.phrases.hasOwnProperty(key)) { 59 | langcode = key; // take the first language code 60 | break; 61 | }; 62 | }; 63 | }; 64 | 65 | // set as selected language code 66 | this.selectedLanguage = langcode; 67 | }; 68 | 69 | this.get = function(key) { 70 | // get key phrase 71 | var str; 72 | 73 | // check if any languages were loaded 74 | if (this.phrases[this.selectedLanguage]) str = this.phrases[this.selectedLanguage][key]; 75 | 76 | // if key does not exist, return the literal key 77 | str = (str || key); 78 | 79 | return str; 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | MultiLang.js 2 | ============ 3 | 4 | MultiLang.js is a JavaScript object to handle multilanguage, localisation, JSON file loading, detect default language, detect non-existing phrases etc. 5 | 6 | It contains a working example page and a JSON example file with multiple languages. Initially created for Phaser.js games, but it can be applied to any JavaScript project or website. 7 | 8 | How to use 9 | ---------- 10 | Store two or more translations in a JSON file, for example `languages.json` like this: 11 | 12 | { 13 | "en": { 14 | "Hello": "Hello World!", 15 | "SaveChanges": "Do you want to save the changes?", 16 | "Yes": "Yes", 17 | "No": "No" 18 | }, 19 | "fr": { 20 | "Hello": "Bonjour à tous!", 21 | "SaveChanges": "Voulez-vous sauvegarder les modifications?", 22 | "Yes": "Oui", 23 | "No": "Non" 24 | }, 25 | "zh": { 26 | "Hello": "大家好", 27 | "SaveChanges": "是否要保存更改?", 28 | "Yes": "要", 29 | "No": "不要" 30 | } 31 | } 32 | 33 | And then MultiLang.js can be used like this: 34 | 35 | var multilang = new MultiLang('languages.json'); 36 | multilang.setLanguage('fr'); 37 | alert( multilang.get('Hello') ); 38 | 39 | Manage translations 40 | ------------------- 41 | Keeping track of all the translated strings can be a challenge, even for smaller projects. See this [Excel spreadsheet](https://github.com/BdR76/Manage-translations) to manage translations more easily. The translator(s) can fill the spreadsheet with translated texts and the JSON file can then be generated using a Excel VBA or LibreOffice macro. For more information [see here](http://stackoverflow.com/questions/20324967/localize-multi-plattform-projects-consolidate-string-files/20330497#20330497). 42 | 43 | Also see the [Manage translations](https://github.com/BdR76/Manage-translations) github page 44 | 45 | spreadsheet --> macro --> JSON 46 | XML strings 47 | String.localization 48 | resx 49 | 50 | History 51 | ------- 52 | 2016-oct-28 upload project on github 53 | 54 | Send any questions or comments to Bas de Reuver - bdr1976@gmail.com 55 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* ===== GENERAL ===== */ 2 | body, html { 3 | font-family:'Gotham Rounded A', 'Gotham Rounded B', sans-serif; 4 | font-size: 24px; 5 | } 6 | div { 7 | background-color: #E0E0E0; 8 | padding: 10px 10px 10px 10px; 9 | margin: 10px 10px 10px 10px; 10 | width: 500px; 11 | display: inline-block; 12 | } 13 | 14 | div.gr { 15 | background-color: #4CAF50; 16 | color: #fff; 17 | padding: 10px; 18 | float: left; 19 | width: 100px; 20 | } 21 | 22 | div.rd { 23 | color: #fff; 24 | float: left; 25 | background-color: #AF4C50; 26 | padding: 10px; 27 | width: 100px; 28 | } 29 | 30 | div.bl { 31 | color: #fff; 32 | background-color: #4C50AF; 33 | padding: 10px; 34 | width: 200px; 35 | } 36 | --------------------------------------------------------------------------------