Skip to content

Commit

Permalink
feat: support zh-hant for DeepL (#711)
Browse files Browse the repository at this point in the history
* feat: support zh-hant for DeepL

* fix: remove unused webView transation for DeepL

* style: format code
  • Loading branch information
tisfeng authored Nov 1, 2024
1 parent 0bbbe7f commit 4c6658d
Showing 1 changed file with 88 additions and 124 deletions.
212 changes: 88 additions & 124 deletions Easydict/objc/Service/DeepL/EZDeepLTranslate.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,13 @@
//

#import "EZDeepLTranslate.h"
#import "EZWebViewTranslator.h"
#import "EZError.h"
#import "EZQueryResult+EZDeepLTranslateResponse.h"

static NSString *kDeepLTranslateURL = @"https://www.deepl.com/translator";

@interface EZDeepLTranslate ()

@property (nonatomic, strong) EZWebViewTranslator *webViewTranslator;

@property (nonatomic, copy) NSString *authKey;
@property (nonatomic, copy) NSString *deepLTranslateEndPointKey;
@property (nonatomic, assign) EZDeepLTranslationAPI apiType;
Expand All @@ -25,23 +22,6 @@ @interface EZDeepLTranslate ()

@implementation EZDeepLTranslate

- (instancetype)init {
if (self = [super init]) {

}
return self;
}

- (EZWebViewTranslator *)webViewTranslator {
if (!_webViewTranslator) {
NSString *selector = @"#target-dummydiv";
_webViewTranslator = [[EZWebViewTranslator alloc] init];
_webViewTranslator.querySelector = selector;
_webViewTranslator.queryModel = self.queryModel;
}
return _webViewTranslator;
}

- (NSString *)authKey {
// easydict://writeKeyValue?EZDeepLAuthKey=xxx
NSString *authKey = [[NSUserDefaults standardUserDefaults] stringForKey:EZDeepLAuthKey] ?: @"";
Expand Down Expand Up @@ -77,22 +57,24 @@ - (NSString *)link {
// https://www.deepl.com/translator#en/zh/good
- (nullable NSString *)wordLink:(EZQueryModel *)queryModel {
NSString *from = [self languageCodeForLanguage:queryModel.queryFromLanguage];
from = [self removeLanguageVariant:from];

NSString *to = [self languageCodeForLanguage:queryModel.queryTargetLanguage];
NSString *text = [queryModel.queryText stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];

/**
!!!: need to convert '/' to '%5C%2F'
e.g. https://www.deepl.com/translator#en/zh/computer%5C%2FFserver
e.g. https://www.deepl.com/translator#en/zh/computer%5C%2Fserver
FIX: https://github.com/tisfeng/Easydict/issues/60
*/
NSString *encodedText = [text stringByReplacingOccurrencesOfString:@"/" withString:@"%5C%2F"];

if (!from || !to) {
return nil;
}

NSString *url = [NSString stringWithFormat:@"%@#%@/%@/%@", kDeepLTranslateURL, from, to, encodedText];

return url;
Expand All @@ -101,40 +83,40 @@ - (nullable NSString *)wordLink:(EZQueryModel *)queryModel {
// Supported languages: https://www.deepl.com/zh/docs-api/translate-text/
- (MMOrderedDictionary<EZLanguage, NSString *> *)supportLanguagesDictionary {
MMOrderedDictionary *orderedDict = [[MMOrderedDictionary alloc] initWithKeysAndObjects:
EZLanguageAuto, @"auto",
EZLanguageSimplifiedChinese, @"zh",
EZLanguageTraditionalChinese, @"zh",
EZLanguageEnglish, @"en",
EZLanguageJapanese, @"ja",
EZLanguageKorean, @"ko",
EZLanguageFrench, @"fr",
EZLanguageSpanish, @"es",
EZLanguagePortuguese, @"pt",
EZLanguageBrazilianPortuguese, @"pt-BR",
EZLanguageItalian, @"it",
EZLanguageGerman, @"de",
EZLanguageRussian, @"ru",
EZLanguageSwedish, @"sv",
EZLanguageRomanian, @"ro",
EZLanguageSlovak, @"sk",
EZLanguageDutch, @"nl",
EZLanguageHungarian, @"hu",
EZLanguageGreek, @"el",
EZLanguageDanish, @"da",
EZLanguageFinnish, @"fi",
EZLanguagePolish, @"pl",
EZLanguageCzech, @"cs",
EZLanguageTurkish, @"tr",
EZLanguageLithuanian, @"lt",
EZLanguageLatvian, @"lv",
EZLanguageUkrainian, @"uk",
EZLanguageBulgarian, @"bg",
EZLanguageIndonesian, @"id",
EZLanguageSlovenian, @"sl",
EZLanguageEstonian, @"et",
EZLanguageNorwegian, @"nb",
EZLanguageArabic, @"ar",
nil];
EZLanguageAuto, @"auto",
EZLanguageSimplifiedChinese, @"zh-hans",
EZLanguageTraditionalChinese, @"zh-hant",
EZLanguageEnglish, @"en",
EZLanguageJapanese, @"ja",
EZLanguageKorean, @"ko",
EZLanguageFrench, @"fr",
EZLanguageSpanish, @"es",
EZLanguagePortuguese, @"pt-PT",
EZLanguageBrazilianPortuguese, @"pt-BR",
EZLanguageItalian, @"it",
EZLanguageGerman, @"de",
EZLanguageRussian, @"ru",
EZLanguageSwedish, @"sv",
EZLanguageRomanian, @"ro",
EZLanguageSlovak, @"sk",
EZLanguageDutch, @"nl",
EZLanguageHungarian, @"hu",
EZLanguageGreek, @"el",
EZLanguageDanish, @"da",
EZLanguageFinnish, @"fi",
EZLanguagePolish, @"pl",
EZLanguageCzech, @"cs",
EZLanguageTurkish, @"tr",
EZLanguageLithuanian, @"lt",
EZLanguageLatvian, @"lv",
EZLanguageUkrainian, @"uk",
EZLanguageBulgarian, @"bg",
EZLanguageIndonesian, @"id",
EZLanguageSlovenian, @"sl",
EZLanguageEstonian, @"et",
EZLanguageNorwegian, @"nb",
EZLanguageArabic, @"ar",
nil];
return orderedDict;
}

Expand All @@ -154,59 +136,29 @@ - (BOOL)autoConvertTraditionalChinese {
return YES;
}

#pragma mark - WebView Translate

- (void)webViewTranslate:(nonnull void (^)(EZQueryResult *, NSError *_Nullable))completion {
NSString *wordLink = [self wordLink:self.queryModel];

mm_weakify(self);
[self.queryModel setStopBlock:^{
mm_strongify(self);
[self.webViewTranslator resetWebView];
} serviceType:self.serviceType];

[self.webViewTranslator queryTranslateURL:wordLink completionHandler:^(NSArray<NSString *> *_Nonnull texts, NSError *_Nonnull error) {
if ([self.queryModel isServiceStopped:self.serviceType]) {
return;
}

self.result.translatedResults = texts;
completion(self.result, error);
}];

// CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
// NSString *monitorURL = @"https://www2.deepl.com/jsonrpc?method=LMT_handle_jobs";
// [self.webViewTranslator monitorBaseURLString:monitorURL
// loadURL:self.wordLink
// completionHandler:^(NSURLResponse *_Nonnull response, id _Nullable responseObject, NSError *_Nullable error) {
// CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
// MMLogInfo(@"API deepL cost: %.1f ms", (endTime - startTime) * 1000); // cost ~2s
//
// MMLogInfo(@"deepL responseObject: %@", responseObject);
// }];
}

#pragma mark - DeepL Web Translate

/// DeepL web translate. Ref: https://github.com/akl7777777/bob-plugin-akl-deepl-free-translate/blob/9d194783b3eb8b3a82f21bcfbbaf29d6b28c2761/src/main.js
- (void)deepLWebTranslate:(NSString *)text from:(EZLanguage)from to:(EZLanguage)to completion:(void (^)(EZQueryResult *, NSError *_Nullable))completion {
NSString *souceLangCode = [self languageCodeForLanguage:from];
NSString *sourceLangCode = [self languageCodeForLanguage:from];
sourceLangCode = [self removeLanguageVariant:sourceLangCode];

NSString *regionalVariant = [self languageCodeForLanguage:to];
NSString *targetLangCode = [regionalVariant componentsSeparatedByString:@"-"].firstObject; // pt-PT, pt-BR

NSString *url = @"https://"
@"www2."
@"deepl.com"
@"/jsonrpc";
@"www2."
@"deepl.com"
@"/jsonrpc";

NSInteger ID = [self getRandomNumber];
NSInteger iCount = [self getICount:text];
NSTimeInterval ts = [self getTimeStampWithIcount:iCount];

NSMutableDictionary *params = @{
@"texts" : @[ @{@"text" : text, @"requestAlternatives" : @(3)} ],
@"splitting" : @"newlines",
@"lang" : @{@"source_lang_user_selected" : souceLangCode, @"target_lang" : targetLangCode},
@"lang" : @{@"source_lang_user_selected" : sourceLangCode, @"target_lang" : targetLangCode},
@"timestamp" : @(ts),
}.mutableCopy;

Expand All @@ -226,7 +178,7 @@ - (void)deepLWebTranslate:(NSString *)text from:(EZLanguage)from to:(EZLanguage)
@"id" : @(ID),
@"params" : params
};
// MMLogInfo(@"postData: %@", postData);
// MMLogInfo(@"postData: %@", postData);

NSString *postStr = [postData mj_JSONString];
if ((ID + 5) % 29 == 0 || (ID + 3) % 13 == 0) {
Expand All @@ -242,28 +194,28 @@ - (void)deepLWebTranslate:(NSString *)text from:(EZLanguage)from to:(EZLanguage)

AFURLSessionManager *manager = [[AFURLSessionManager alloc] init];
manager.session.configuration.timeoutIntervalForRequest = EZNetWorkTimeoutInterval;

CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();

NSURLSessionTask *task = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse *_Nonnull response, id _Nullable responseObject, NSError *_Nullable error) {
if ([self.queryModel isServiceStopped:self.serviceType]) {
return;
}

if (error.code == NSURLErrorCancelled) {
return;
}

if (error) {
MMLogError(@"deepLWebTranslate error: %@", error);
EZError *ezError = [EZError errorWithNSError:error];

BOOL useOfficialAPI = (self.authKey.length > 0) && (self.apiType == EZDeepLTranslationAPIWebFirst);
if (useOfficialAPI) {
[self deepLTranslate:text from:from to:to completion:completion];
return;
}

NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
if (errorData) {
/**
Expand All @@ -288,7 +240,7 @@ - (void)deepLWebTranslate:(NSString *)text from:(EZLanguage)from to:(EZLanguage)
completion(self.result, ezError);
return;
}

CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
MMLogInfo(@"deepLWebTranslate cost: %.1f ms", (endTime - startTime) * 1000);

Expand All @@ -302,7 +254,7 @@ - (void)deepLWebTranslate:(NSString *)text from:(EZLanguage)from to:(EZLanguage)
completion(self.result, nil);
}];
[task resume];

[self.queryModel setStopBlock:^{
[task cancel];
} serviceType:self.serviceType];
Expand Down Expand Up @@ -330,63 +282,65 @@ - (NSInteger)getTimeStampWithIcount:(NSInteger)iCount {

#pragma mark - DeepL Official Translate API

- (void)deepLTranslate:(NSString *)text from:(EZLanguage)from to:(EZLanguage)to completion:(void (^)(EZQueryResult *, NSError *_Nullable))completion{
- (void)deepLTranslate:(NSString *)text from:(EZLanguage)from to:(EZLanguage)to completion:(void (^)(EZQueryResult *, NSError *_Nullable))completion {
// Docs: https://www.deepl.com/zh/docs-api/translating-text

NSString *souceLangCode = [self languageCodeForLanguage:from];
souceLangCode = [self removeLanguageVariant:souceLangCode];

NSString *targetLangCode = [self languageCodeForLanguage:to];

// DeepL api free and deepL pro api use different url host.
BOOL isFreeKey = [self.authKey hasSuffix:@":fx"];
NSString *host = isFreeKey ? @"https://api-free.deepl.com": @"https://api.deepl.com";
NSString *host = isFreeKey ? @"https://api-free.deepl.com" : @"https://api.deepl.com";
NSString *url = [NSString stringWithFormat:@"%@/v2/translate", host];

if (self.deepLTranslateEndPointKey.length) {
url = self.deepLTranslateEndPointKey;
}

NSDictionary *params = @{
@"text": text,
@"source_lang": souceLangCode,
@"target_lang": targetLangCode
@"text" : text,
@"source_lang" : souceLangCode,
@"target_lang" : targetLangCode
};

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.session.configuration.timeoutIntervalForRequest = EZNetWorkTimeoutInterval;

NSString *authorization = [NSString stringWithFormat:@"DeepL-Auth-Key %@", self.authKey];
[manager.requestSerializer setValue:authorization forHTTPHeaderField:@"Authorization"];

CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();

NSURLSessionTask *task = [manager POST:url parameters:params progress:nil success:^(NSURLSessionDataTask *_Nonnull task, id _Nullable responseObject) {
CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
MMLogInfo(@"deepLTranslate cost: %.1f ms", (endTime - startTime) * 1000);

self.result.translatedResults = [self parseOfficialResponseObject:responseObject];
self.result.raw = responseObject;
completion(self.result, nil);
} failure:^(NSURLSessionDataTask *_Nullable task, NSError *_Nonnull error) {
if ([self.queryModel isServiceStopped:self.serviceType]) {
return;
}

if (error.code == NSURLErrorCancelled) {
return;
}

MMLogError(@"deepLTranslate error: %@", error);

if (self.apiType == EZDeepLTranslationAPIOfficialFirst) {
[self deepLWebTranslate:text from:from to:to completion:completion];
return;
}

EZError *ezError = [EZError errorWithNSError:error];

completion(self.result, ezError);
}];

[self.queryModel setStopBlock:^{
[task cancel];
} serviceType:self.serviceType];
Expand All @@ -410,4 +364,14 @@ - (void)deepLTranslate:(NSString *)text from:(EZLanguage)from to:(EZLanguage)to
return translatedTextArray;
}

#pragma mark -

/// Remove language variant, e.g. zh-hans --> zh, pt-BR --> pt
/// Since DeepL API source language code is different from the target language code, it has no variant.
/// DeepL Docs: https://developers.deepl.com/docs/zh/resources/supported-languages#source-languages
- (NSString *)removeLanguageVariant:(NSString *)languageCode {
NSString *sourceLangCode = [languageCode componentsSeparatedByString:@"-"].firstObject;
return sourceLangCode;
}

@end

0 comments on commit 4c6658d

Please sign in to comment.