Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support zh-hant for DeepL #711

Merged
merged 3 commits into from
Nov 1, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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";
phlpsong marked this conversation as resolved.
Show resolved Hide resolved

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