1+ using System . Reflection ;
2+ using DeepL ;
3+ using Nop . Core . Domain . Localization ;
4+ using Nop . Core . Domain . Translation ;
5+ using Nop . Services . Localization ;
6+ using Nop . Services . Logging ;
7+ using Nop . Web . Framework . Models ;
8+ using Nop . Web . Framework . Models . Translation ;
9+
10+ namespace Nop . Web . Framework . Factories ;
11+
12+ /// <summary>
13+ /// Represents translation model factory implementation
14+ /// </summary>
15+ public partial class TranslationModelFactory : ITranslationModelFactory
16+ {
17+ #region Fields
18+
19+ protected readonly ILanguageService _languageService ;
20+ protected readonly ILocalizationService _localizationService ;
21+ protected readonly ILogger _logger ;
22+ protected TranslationClientHelper _translationClientHelper ;
23+ protected readonly TranslationSettings _translationSettings ;
24+
25+ #endregion
26+
27+ #region Ctor
28+
29+ public TranslationModelFactory ( ILanguageService languageService ,
30+ ILocalizationService localizationService ,
31+ ILogger logger ,
32+ TranslationSettings translationSettings )
33+ {
34+ _languageService = languageService ;
35+ _localizationService = localizationService ;
36+ _logger = logger ;
37+ _translationClientHelper = null ;
38+ _translationSettings = translationSettings ;
39+ }
40+
41+ #endregion
42+
43+ #region Methods
44+
45+ /// <summary>
46+ /// Prepare translation model by the passed localized model
47+ /// </summary>
48+ /// <typeparam name="T">Localized model type</typeparam>
49+ /// <param name="model">The localized model to translate</param>
50+ /// <param name="propertiesToTranslate">List of properties which should be translated</param>
51+ /// <returns>
52+ /// A task that represents the asynchronous operation
53+ /// The task result contains the translation model
54+ /// </returns>
55+ public virtual async Task < TranslationModel > PrepareTranslationModelAsync < T > ( ILocalizedModel < T > model ,
56+ params string [ ] propertiesToTranslate )
57+ where T : ILocalizedLocaleModel
58+ {
59+ return await PrepareTranslationModelAsync ( model , propertiesToTranslate . Select ( p => ( p , false ) ) . ToArray ( ) ) ;
60+ }
61+
62+ /// <summary>
63+ /// Prepare translation model by the passed localized model
64+ /// </summary>
65+ /// <typeparam name="T">Localized model type</typeparam>
66+ /// <param name="model">The localized model to translate</param>
67+ /// <param name="propertiesToTranslate">List of properties which should be translated</param>
68+ /// <returns>
69+ /// A task that represents the asynchronous operation
70+ /// The task result contains the translation model
71+ /// </returns>
72+ public virtual async Task < TranslationModel > PrepareTranslationModelAsync < T > ( ILocalizedModel < T > model ,
73+ params ( string PropertyName , bool IsHtml ) [ ] propertiesToTranslate )
74+ where T : ILocalizedLocaleModel
75+ {
76+ _translationClientHelper ??= new TranslationClientHelper ( _translationSettings ) ;
77+
78+ var result = new TranslationModel ( ) ;
79+
80+ var properties = propertiesToTranslate . Select ( p => new KeyValuePair < string , bool > ( p . PropertyName , p . IsHtml ) ) . ToDictionary ( ) ;
81+
82+ //get model properties to use as original text for translation
83+ var modelProperties = model . GetType ( ) . GetProperties ( )
84+ . Where ( propertyFilter )
85+ . ToList ( ) ;
86+
87+ //get properties to save translated text
88+ var localizedProperties = typeof ( T ) . GetProperties ( )
89+ . Where ( propertyFilter )
90+ . ToList ( ) ;
91+
92+ //get original language
93+ var originalLanguage = await _languageService . GetLanguageByIdAsync ( _translationSettings . TranslateFromLanguageId ) ;
94+ var position = 0 ;
95+
96+ //the loop by locales which should be translated
97+ foreach ( var modelLocale in model . Locales )
98+ {
99+ position ++ ;
100+
101+ //we ignore the original language
102+ if ( modelLocale . LanguageId == _translationSettings . TranslateFromLanguageId )
103+ continue ;
104+
105+ //and languages which should be ignored
106+ if ( _translationSettings . NotTranslateLanguages . Contains ( modelLocale . LanguageId ) )
107+ continue ;
108+
109+ //get target languages to translation for
110+ var translateToLanguage = await _languageService . GetLanguageByIdAsync ( modelLocale . LanguageId ) ;
111+
112+ //the loop by properties which should be translated
113+ foreach ( var prop in localizedProperties )
114+ {
115+ //get the current value of property
116+ var currentTranslatedText = prop . GetValue ( modelLocale , null ) ? . ToString ( ) ;
117+
118+ //ignore the property which already has a value
119+ if ( ! string . IsNullOrEmpty ( currentTranslatedText ) )
120+ continue ;
121+
122+ //get original text to translate
123+ var originText = modelProperties . FirstOrDefault ( p => p . Name . Equals ( prop . Name ) ) ? . GetValue ( model , null ) ? . ToString ( ) ;
124+
125+ //ignore the empty original text
126+ if ( string . IsNullOrEmpty ( originText ) )
127+ continue ;
128+
129+ try
130+ {
131+ var translatedText = await _translationClientHelper . TranslateAsync ( originalLanguage , originText , translateToLanguage , properties [ prop . Name ] ) ;
132+ if ( string . IsNullOrEmpty ( translatedText ) )
133+ continue ;
134+
135+ //set translated text to property
136+ prop . SetValue ( modelLocale , translatedText ) ;
137+ var inputName = $ "{ nameof ( model . Locales ) } _{ position - 1 } __{ prop . Name } ";
138+ result . Translations . Add ( new ( )
139+ {
140+ Name = inputName ,
141+ Value = translatedText ,
142+ OriginValue = originText ,
143+ Language = translateToLanguage . UniqueSeoCode ,
144+ OriginLanguage = originalLanguage . UniqueSeoCode
145+ } ) ;
146+ }
147+ catch ( Exception e )
148+ {
149+ var serviceName = await _localizationService . GetLocalizedEnumAsync ( ( TranslationServiceType ) _translationSettings . TranslationServiceId ) ;
150+ var errorMessage = $ "{ serviceName } : { e . Message } ";
151+ await _logger . ErrorAsync ( errorMessage , e ) ;
152+ result . HasErrors = true ;
153+
154+ //DeepL: stop translate if one of the languages aren't support
155+ //to reduce error count
156+ if ( e . Message . Contains ( "Value for 'target_lang' not supported" , StringComparison . InvariantCultureIgnoreCase ) || e . Message . Contains ( "Value for 'source_lang' not supported" , StringComparison . InvariantCultureIgnoreCase ) )
157+ break ;
158+ }
159+ }
160+ }
161+
162+ return result ;
163+
164+ //filter for get only string property which should be translated
165+ bool propertyFilter ( PropertyInfo propertyInfo )
166+ {
167+ return propertyInfo . PropertyType == typeof ( string ) && properties . ContainsKey ( propertyInfo . Name ) ;
168+ }
169+ }
170+
171+ #endregion
172+
173+ #region Nested class
174+
175+ /// <summary>
176+ /// Helper class to translation client
177+ /// </summary>
178+ protected class TranslationClientHelper
179+ {
180+ #region Fields
181+
182+ protected readonly Google . Cloud . Translation . V2 . TranslationClient _googleClient ;
183+ protected readonly DeepLClient _deeplClient ;
184+
185+ #endregion
186+
187+ #region Ctor
188+
189+ public TranslationClientHelper ( TranslationSettings translationSettings )
190+ {
191+ switch ( ( TranslationServiceType ) translationSettings . TranslationServiceId )
192+ {
193+ case TranslationServiceType . GoogleTranslate :
194+ {
195+ if ( ! string . IsNullOrEmpty ( translationSettings . GoogleApiKey ) )
196+ _googleClient = Google . Cloud . Translation . V2 . TranslationClient . CreateFromApiKey ( translationSettings . GoogleApiKey ) ;
197+ _deeplClient = null ;
198+ }
199+ break ;
200+
201+ case TranslationServiceType . DeepL :
202+ {
203+ if ( ! string . IsNullOrEmpty ( translationSettings . DeepLAuthKey ) )
204+ _deeplClient = new DeepLClient ( translationSettings . DeepLAuthKey ) ;
205+ _googleClient = null ;
206+ }
207+ break ;
208+ }
209+ }
210+
211+ #endregion
212+
213+ #region Methods
214+
215+ /// <summary>
216+ /// Translate text or html
217+ /// </summary>
218+ /// <param name="originalLanguage">The language to translate from</param>
219+ /// <param name="originText">The text or HTML to translate</param>
220+ /// <param name="targetLanguage">The target language to translate</param>
221+ /// <param name="isHtml">Indicate whether the text to translate should be considered as HTML</param>
222+ /// <returns>
223+ /// A task that represents the asynchronous operation
224+ /// The task result contains the translated text
225+ /// </returns>
226+ public virtual async Task < string > TranslateAsync ( Language originalLanguage , string originText , Language targetLanguage , bool isHtml )
227+ {
228+ if ( _googleClient != null )
229+ {
230+ var response = isHtml
231+ ? await _googleClient . TranslateHtmlAsync ( originText , targetLanguage . UniqueSeoCode , originalLanguage . UniqueSeoCode )
232+ : await _googleClient . TranslateTextAsync ( originText , targetLanguage . UniqueSeoCode , originalLanguage . UniqueSeoCode ) ;
233+
234+ return response . TranslatedText ;
235+ }
236+
237+ if ( _deeplClient != null )
238+ {
239+ var response = await _deeplClient . TranslateTextAsync ( originText , originalLanguage . UniqueSeoCode , targetLanguage . UniqueSeoCode , new TextTranslateOptions
240+ {
241+ TagHandling = isHtml ? "html" : null
242+ } ) ;
243+
244+ return response . Text ;
245+ }
246+
247+ return string . Empty ;
248+ }
249+
250+ #endregion
251+ }
252+
253+ #endregion
254+ }
0 commit comments