1 /***************************************************************************
2  *   Free Heroes of Might and Magic II: https://github.com/ihhub/fheroes2  *
3  *   Copyright (C) 2021                                                    *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 
21 #include "ui_language.h"
22 #include "agg.h"
23 #include "agg_image.h"
24 #include "icn.h"
25 #include "settings.h"
26 #include "tools.h"
27 #include "translations.h"
28 
29 #include <cassert>
30 #include <map>
31 #include <set>
32 
33 namespace
34 {
35     const std::map<uint32_t, fheroes2::SupportedLanguage> languageCRC32 = { { 0x406967B9, fheroes2::SupportedLanguage::French }, // GoG version
36                                                                             { 0x04745D1D, fheroes2::SupportedLanguage::German }, // GoG version
37                                                                             { 0x88774771, fheroes2::SupportedLanguage::Polish }, // GoG version
38                                                                             { 0xDB10FFD8, fheroes2::SupportedLanguage::Russian }, // XXI Vek version
39                                                                             { 0xD5CF8AF3, fheroes2::SupportedLanguage::Russian }, // Buka version
40                                                                             { 0x219B3124, fheroes2::SupportedLanguage::Italian } };
41 
42     // Strings in this map must in lower case and non translatable.
43     const std::map<std::string, fheroes2::SupportedLanguage> languageName
44         = { { "pl", fheroes2::SupportedLanguage::Polish },      { "polish", fheroes2::SupportedLanguage::Polish },   { "de", fheroes2::SupportedLanguage::German },
45             { "german", fheroes2::SupportedLanguage::German },  { "fr", fheroes2::SupportedLanguage::French },       { "french", fheroes2::SupportedLanguage::French },
46             { "ru", fheroes2::SupportedLanguage::Russian },     { "russian", fheroes2::SupportedLanguage::Russian }, { "it", fheroes2::SupportedLanguage::Italian },
47             { "italian", fheroes2::SupportedLanguage::Italian } };
48 
getResourceLanguage()49     fheroes2::SupportedLanguage getResourceLanguage()
50     {
51         const std::vector<uint8_t> & data = ::AGG::ReadChunk( ICN::GetString( ICN::FONT ) );
52         if ( data.empty() ) {
53             // How is it possible to run the game without a font?
54             assert( 0 );
55             return fheroes2::SupportedLanguage::English;
56         }
57 
58         const uint32_t crc32 = fheroes2::calculateCRC32( data.data(), data.size() );
59         auto iter = languageCRC32.find( crc32 );
60         if ( iter == languageCRC32.end() ) {
61             return fheroes2::SupportedLanguage::English;
62         }
63 
64         return iter->second;
65     }
66 }
67 
68 namespace fheroes2
69 {
LanguageSwitcher(const SupportedLanguage language)70     LanguageSwitcher::LanguageSwitcher( const SupportedLanguage language )
71         : _currentLanguage( Settings::Get().getGameLanguage() )
72     {
73         Settings::Get().setGameLanguage( getLanguageAbbreviation( language ) );
74     }
75 
~LanguageSwitcher()76     LanguageSwitcher::~LanguageSwitcher()
77     {
78         Settings::Get().setGameLanguage( _currentLanguage );
79     }
80 
getSupportedLanguages()81     std::vector<SupportedLanguage> getSupportedLanguages()
82     {
83         std::vector<SupportedLanguage> languages;
84 
85         const SupportedLanguage resourceLanguage = getResourceLanguage();
86         if ( resourceLanguage != SupportedLanguage::English ) {
87             languages.emplace_back( resourceLanguage );
88         }
89 
90         const std::set<SupportedLanguage> possibleLanguages{ SupportedLanguage::French, SupportedLanguage::Polish, SupportedLanguage::German, SupportedLanguage::Russian,
91                                                              SupportedLanguage::Italian };
92 
93         for ( const SupportedLanguage language : possibleLanguages ) {
94             if ( language != resourceLanguage && AGG::isAlphabetSupported( language ) ) {
95                 languages.emplace_back( language );
96             }
97         }
98 
99         Settings & conf = Settings::Get();
100 
101         fheroes2::SupportedLanguage currentLanguage = fheroes2::getLanguageFromAbbreviation( conf.getGameLanguage() );
102 
103         std::vector<fheroes2::SupportedLanguage> validSupportedLanguages{ fheroes2::SupportedLanguage::English };
104 
105         for ( fheroes2::SupportedLanguage language : languages ) {
106             if ( conf.setGameLanguage( fheroes2::getLanguageAbbreviation( language ) ) ) {
107                 validSupportedLanguages.emplace_back( language );
108             }
109         }
110 
111         conf.setGameLanguage( fheroes2::getLanguageAbbreviation( currentLanguage ) );
112 
113         assert( !validSupportedLanguages.empty() );
114 
115         return validSupportedLanguages;
116     }
117 
getLanguageName(const SupportedLanguage language)118     const char * getLanguageName( const SupportedLanguage language )
119     {
120         switch ( language ) {
121         case SupportedLanguage::English:
122             return _( "English" );
123         case SupportedLanguage::French:
124             return _( "French" );
125         case SupportedLanguage::Polish:
126             return _( "Polish" );
127         case SupportedLanguage::German:
128             return _( "German" );
129         case SupportedLanguage::Russian:
130             return _( "Russian" );
131         case SupportedLanguage::Italian:
132             return _( "Italian" );
133         default:
134             // Did you add a new language? Please add the code to handle it.
135             assert( 0 );
136             return nullptr;
137         }
138     }
139 
getLanguageAbbreviation(const SupportedLanguage language)140     const char * getLanguageAbbreviation( const SupportedLanguage language )
141     {
142         switch ( language ) {
143         case SupportedLanguage::English:
144             return ""; // English is a special case. It always returns an empty string as it's a default language.
145         case SupportedLanguage::French:
146             return "fr";
147         case SupportedLanguage::Polish:
148             return "pl";
149         case SupportedLanguage::German:
150             return "de";
151         case SupportedLanguage::Russian:
152             return "ru";
153         case SupportedLanguage::Italian:
154             return "it";
155         default:
156             // Did you add a new language? Please add the code to handle it.
157             assert( 0 );
158             return nullptr;
159         }
160     }
161 
getLanguageFromAbbreviation(const std::string & abbreviation)162     SupportedLanguage getLanguageFromAbbreviation( const std::string & abbreviation )
163     {
164         if ( abbreviation.empty() ) {
165             return SupportedLanguage::English;
166         }
167 
168         const std::string name( StringLower( abbreviation ) );
169 
170         auto iter = languageName.find( name );
171         if ( iter == languageName.end() ) {
172             // Unsupported language. Fallback to English.
173             return SupportedLanguage::English;
174         }
175 
176         return iter->second;
177     }
178 
updateAlphabet(const std::string & abbreviation)179     void updateAlphabet( const std::string & abbreviation )
180     {
181         const SupportedLanguage language = getLanguageFromAbbreviation( abbreviation );
182         const bool isOriginalResourceLanguage = ( language == SupportedLanguage::English ) || ( language == getResourceLanguage() );
183 
184         AGG::updateAlphabet( language, isOriginalResourceLanguage );
185     }
186 }
187