TMLKit 2.2.0

TMLKit 2.2.0

测试已测试
语言语言 Objective-CObjective C
许可证 MIT
发布上次发布2017年12月
SwiftSwift版本4.0

PashaMichael BerkovichKonstantin Kabanov维护。



 
依赖项
MPColorTools>= 1.6
Noewolf Bar�联网络库>= 0
SSZipArchive>= 0
NVHTarGzip>= 0
SAMKeychain>= 0
AFNetworking>= 0
MZFormSheetPresentationController>= 0
Socket.IO-Client-Swift~> 13.0.0
 

TMLKit 2.2.0

TML for Objective C

TMLKit是一个Objective-C SDK,用于iOS应用程序的集成云翻译解决方案。

它减少了对移动应用程序国际化本地化的步骤。

TMLKit与TranslationExchange.com服务集成,您可以在那里管理整个翻译流程 - 启用语言,邀请翻译人员,管理翻译密钥等更多内容。

您永远无需触摸资源包文件 - 翻译密钥会在应用程序源代码中动态提取并由SDK保持最新。

一旦您的应用程序翻译完成,SDK会自动下载并安装翻译。您无需向App Store提交新应用程序。您只需在TranslationExchange.com上启用新语言,它就会立即在您的应用程序中可用。

要求

iOS 8
CocoaPods

演示应用程序

此存储库包含一个演示应用程序。要运行,请按照以下步骤操作:

$ git clone https://github.com/translationexchange/tml-objc.git
$ cd tml-objc/Demo
$ pod install
$ open Demo.xcworkspace

通过编辑main.m并设置相应的应用程序密钥访问令牌参数来配置TMLKit,以便与您的TranslationExchange项目相匹配。最后,从Xcode中运行应用程序。

演示应用程序运行后,打开侧边栏并启用内联翻译。这将使TMLKit将您的应用程序中所有可本地化的字符串与您的TranslationExchange项目注册。从侧边栏中选择更改语言。您应在这里列出项目中当前使用的所有语言。TMLKit提供了一个默认的语言选择器。创建一个定制的语言选择器也很简单 - 有关详细信息,请参阅TMLLanguageSelectorViewController

转到仪表板并为您的项目添加新语言。现在,切换回演示应用程序,将其置于后台和前台 - 这将触发TMLKit立即更新有关项目的信息,以及随后的可用语言列表。

应用内翻译 模式让您可以直接在应用内进行翻译。请确保您在语言选择器中选择了非默认语言,然后简单地在屏幕上任意文本上轻触并保持。TMLKit 将显示翻译您所点击的字符串的界面。您可以手动添加翻译,或者从底部列表中选择如果您满意的机器翻译。一旦您添加了翻译,关闭翻译视图,就可以看到原始字符串被更新为其翻译。

另一个示例应用程序位于这里

https://github.com/translationexchange/tml-objc-samples-wammer

安装

要通过 CocoaPods 安装 SDK,只需将以下行添加到您的 Podfile 中

pod "TMLKit"

然后运行

pod install

或者,您可以克隆此存储库

$ git clone https://github.com/translationexchange/tml-objc.git
$ cd tml-objc/TMLKit
$ pod install

然后构建 TMLKit.xcodeproj,并将构建框架包含到您的项目中,或者,将 TMLKit.xcodeproj 拖入您的应用程序的 Xcode 项目中,并配置为 链接框架

集成

在您进行集成之前,请访问 https://TranslationExchange.com - 开设一个账户并为您的应用创建一个项目。每个项目都将分配一个唯一的 应用程序密钥访问令牌,您将使用它们来配置 TMLKit。

虽然 应用程序密钥 是必需的,但通过 访问令牌 配置 TMLKit 是可选的,并允许您的应用在服务器上注册新的翻译密钥并利用 应用内翻译 模式。当您发布应用程序时,应删除 访问令牌 并仅提供 应用程序密钥

以下是配置 TMLKit 的方法

#import <TMLKit/TMLKit.h>

int main(int argc, char * argv[])
{
    @autoreleasepool {
        
        [TML sharedInstanceWithApplicationKey:@"<APPLICATION KEY>" accessToken:@"<ACCESS TOKEN>"];
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

注意: TML 能够在解析 NIB 文件时自动本地化字符串。这是一个默认启用的可配置选项。这种自动本地化发生在您的应用委托被应用程序:完成启动选项调用之前。这就是为什么上面的示例使用 main.m 来配置 TMLKit 的原因。如果您不使用自动 NIB 本地化,则可以将其移动到您的应用委托中...

TMLKit 带有一系列宏,用于帮助进行本地化。如果您以前使用过 NSLocalizedString(),那么 TMLLocalizedString() 应该是您所熟悉的。为了在每份文件中避免包含 TMLKit.h,最好将其导入到前置头文件中,同时包括您的 foundation 框架包含项

#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <TMLKit/TMLKit.h>
#endif

如果您的项目使用 NSLocalizedString 并没有使用 TML 标记语言,或者很想看到一些结果,TMLKit 提供了重新定义的 NSLocalizedString 宏。只需添加

#import <TMLKit/TMLKit+NSLocalizedString.h>

但是,您只能通过 TMLLocalizedString() 宏完全使用 TML。TML 支持默认屈折语、复数化程序、上下文化器和语言情况,这对更好的编码和更好的本地化有很大帮助。

它是如何工作的?

TMLKit 会为您做所有艰难的工作。当您使用 TML 的宏(请参阅 TML.h)时,库会自动将翻译密钥注册到翻译交换服务,并实时为您的应用生成资源包。当新的翻译可用时,TMLKit 会通过服务下载最新翻译以保持您的本地缓存是最新的。在运行时,您的应用将使用您本地缓存中的翻译,因此您的用户不会体验到任何延迟。当新的本地化数据可用时,您的应用程序可以动态更新翻译,无需重新启动应用程序。

您还可以在发布应用程序之前将其所有翻译捆绑在一起——允许应用程序在离线模式下工作。默认情况下,每当您的应用程序处于活动状态并且可以连接到互联网时——TMLKit将检查新的翻译发布(通过仪表板发布)。当有更新可用时,将按需下载。默认语言和当前区域设置的翻译将立即下载,当TMLKit被告知更改当前区域设置时,将下载附加数据。

要将翻译与您的应用程序捆绑在一起,只需从仪表板下载已发布的存档,在其名称前添加“tml_”,并将其作为资源包含在您的构建中。生成的文件名应采用“tml_.tar.gz”格式。TMLKit支持zip、tar、tar.gz/tar.gzip格式。

TMLKit还报告分析数据,让您可以看到您的应用程序中使用了哪些语言,用户的默认语言是什么,用户来自哪里等...

国际化 & TML

如果您的应用程序已使用标准的NSNotificationCenter方法进行国际化,您可以简单地在您的.m文件中导入TML+NSLocalizedString.h头文件,TML将接管国际化宏。

#import "<TMLKit/TML+NSLocalizedString.h>"

然而,这些宏相当有限,因为它们不允许使用完整的TML语法(数据令牌等)。

TML还提供自己的宏进行国际化,这显著增强了标准的iOS宏。

基本示例

TMLLocalizedString(label);
TMLLocalizedString(label, description);

TMLLocalizedString(@"Invite");
TMLLocalizedString(@"Invite", @"Invite someone to join the group");

该宏类似于NSNotificationCenter,它不需要可选的注释参数。

与NSNotificationCenter不同,其中第二个参数是为翻译者提供的注释,TML使用描述来具体化键。所以,上面的例子实际上注册了两个不同的键,每个键都会有自己的翻译。

另一个示例

TMLLocalizedString(label, tokens);

TMLLocalizedStringWithTokens(@"You have {count || message}.", @{@"count": @4});
TMLLocalizedStringWithTokens(@"Hello {user}.", @{@"user": @"Michael"});

可以通过许多不同方式传递令牌。如果以原始类型传递令牌,它将用于上下文规则和显示值。如果以类或结构体传递,您可以区分用于上下文规则的对象和将替代令牌的值。

使用令牌的更多示例

User *user  = [[User alloc] initWithName: @"Michael" gender: @"male"];
// will use [user description] for substitution value
TMLLocalizedStringWithTokens(@"Hello {user}.", @{@"user": user})
// second parameter is used for substitution value
TMLLocalizedStringWithTokens(@"Hello {user}.", @{@"user": @[user, user.name]})
TMLLocalizedStringWithTokens(@"Hello {user}.", @{@"user": @[user, @"Mike"]})
NSDictionary *user = @{@"name": @"Michael", @"gender": @"male"};
// can be used for context rules, but not substitution value
TMLLocalizedStringWithTokens(@"{user | Born On}.", @{@"user": user})
TMLLocalizedStringWithTokens(@"Hello {user}.", @{
          @"user": @{@"object": user, @"property": @"name"}
})
TMLLocalizedStringWithTokens(@"Hello {user}.", @{
          @"user": @{@"object": user, @"value": @"Michael"}
})

您可能已经注意到我们正在使用相同的宏并传入各种参数。TMLLocalizedString()的完整语法是

TMLLocalizedString(NSString *localizedString, NSString *description, NSDictionary *tokens, NSDictionary *userOptions);

只有第一个参数是必需的...

TMLLocalizedString(
    @"Hello {user}",  // localized string
    @"A greeting message",  // description
    @{@"user": @"Michael"}  // tokens
)
TMLLocalizedString(
    @"Hello {user}",  // localized string
    @"A greeting message",  // description
    @{@"user": @"Michael"}, // tokens
    @{@"level": @5, @"max-length": @20} // options
)
TMLLocalizedString(
    @"Hello {user}",  // localized string
    @{@"user": @"Michael"}, // tokens
)
TMLLocalizedString(
    @"Hello {user}",  // localized string
    @{}, // no tokens
    @{@"level": @5, @"max-length": @20} // options
)

选项是TML特定的选项。一些用于格式化字符串,一些完全是管理性的。例如,在上面的代码片段中,选项指定只有特定等级的翻译者可以翻译特定等级的键。约束指出此键的翻译可能不超过20个字符。有关详细信息,请参阅wiki.translationexchage.com。

上面所有的宏都假定您正在处理纯文本,并将返回NSString。也可以使用类似数据令牌的方式工作(用‘{}’分隔)。

TMLLocalizedAttributedString(
    @"You have completed [bold: {count || mile}] on your last run.",
    @{@"count": @4.2}
)

请注意,我们现在正在使用TMLLocalizedAttributedString()宏。它与TMLLocalizedString()同义,存在唯一的目的是为了区分返回类型。TMLLocalizedString()返回NSString,TMLLocalizedAttributedString()返回NSAttributedString。其次,您会注意到装饰令牌分隔符为‘[]’方括号,而数据令牌的呢,是用‘{}’括号分隔的。

总的来说,上面的示例将返回

	"You have completed **4.2 miles** on your last run."

TMLKit支持NSAttributedString格式和HTML。以下是HTML等价物

TMLLocalizedString(
    @"You have completed [bold: {count || mile}] on your last run.",
    @{@"count": @4.2},
    @{TMLTokenFormatOptionName: TMLHTMLTokenFormatString}
)

这导致

	"You have completed <strong>4.2 miles</strong> on your last run."

请注意,如果您期望得到NSString,请使用TMLLocalizedString();如果您期望到NSAttributedString,请使用TMLLocalizedAttributedString();

默认令牌

还可以定义默认令牌并在代码中通过名称引用它们,而不是提供相同的数据结构。这样可以使代码更简洁、更一致。

TMLConfiguration *config = TMLSharedConfiguration();
// Data Tokens
[config setDefaultTokenValue: @"My App Name"
                     forName: @"app_name"
                        type: TMLDataTokenType];

// Decorated Tokens with attributed strings
[config setDefaultTokenValue: @{
                   @"font": @{
                      @"name": @"system",
                      @"size": @12,
                      @"type": @"italic"
                   },
                   @"color": @"blue"
                 }
                     forName: @"bold"
                        type: TMLDecorationTokenType
                      format: TMLAttributedTokenFormat];

[config setDefaultTokenValue: @{
                   @"shadow": @{
                      @"offset": @1,1,
                      @"radius": @0.5,
                      @"color": @"grey"
                   },
                   @"color": @"black"
                                 }
                     forName: @"shadow"
                        type: TMLDecorationTokenType
                      format: TMLAttributedTokenFormat];

[config setDefaultTokenValue: @{
                   @"attributes": @{
                     UIFontDescriptorNameAttribute: @"Arial"
                     UIFontDescriptorNameAttribute: @(12.), 
                     UIFontDescriptorSymbolicTraits: UIFontDescriptorTraitItalic
                   }
                 }
                     forName: @"italic"
                        type: TMLDecorationTokenType
                      format: TMLAttributedTokenFormat];

// Decorated tokens with HTML strings
[config setDefaultTokenValue: @"<strong>{$0}</strong>"
                     forName: @"bold"
                        type: TMLDecorationTokenType
                      format: TMLHTMLTokenFormat];

[config setDefaultTokenValue: @"<span style='color:green'>{$0}</span>"
                     forName: @"green"
                        type: TMLDecorationTokenType
                      format: TMLHTMLTokenFormat];

或者,您可以提供行内的令牌值,这将覆盖默认令牌定义。

以下示例将使用上述预定义的令牌。

TMLLocalizedAttributedString(@"Hello [bold: World]");
TMLLocalizedAttributedString(@"[italic: Hello World]");
TMLLocalizedAttributedString(@"This [bold: technology is [shadow: very cool]].");

请注意,“非常酷”将会加粗且有阴影。嵌套令牌会继承父令牌的特性。

TMLLocalizedAttributedString() 的好处

使用 TML 与 NSAttributedString 一起的好处是标签在上下文中得到翻译。如果您在没有使用 TML 的上述示例中尝试,您将得到以下类似的代码:

NSDictionary *bold = @{[UIFont boldSystemFontOfSize:@12], NSFontAttributeName};

NSMutableAttributedString *str = [[NSMutableAttributedString alloc] init];
[str appendString : NSLocalizedString(@"You have completed ")];

if (distance_in_miles == 1)
   [str appendAttributedString:
          [[NSAttributedString alloc] initWithString: NSLocalizedString(@"1 mile")]
                      attributes: bold];
else
   [str appendAttributedString:
          [[NSAttributedString alloc] initWithString:
              [NSString stringWithFormat: NSLocalizedString(@"%d miles"), distance_in_miles]]
                      attributes: bold];

[str appendString: NSLocalizedString(@" on your last run.")];

以上代码有以下问题:

  • (distance_in_miles == 1) 检查在具有更复杂数字规则的俄语或阿拉伯语等语言中会失败。
  • "你完成了 " 和 "你上次的跑步" 将在整句的上下文之外被翻译。在有些语言中,某些单词可能需要调换位置,因此它在上下文和组合上面都失败了。
  • "1 英里" 和 "%d 英里" 也将被翻译到上下文之外。

以上所有代码都可以用一行 TML 替换。

TMLLocalizedAttributedString(
    @"You have completed [bold: {count || mile}] on your last run.",
    @{@"count": @4.2}
)

这可以很容易地翻译成俄语。

"Вы пробежали [bold: {count || милю, мили, миль}] в вашем последнем забеге."

由于有像希伯来语这样的语言依赖于查看用户的性别,每次您提到 "你" 时,都应该始终传递查看用户作为令牌。

TMLLocalizedAttributedString(@"You have completed [bold: {count || mile}] on your last run.", @{@"count": @4.2, @"viewing_user": currentUser})

或者更好,将其设置在配置中,然后您就不再需要将其作为令牌传递。

TMLSharedConfiguration().viewingUser = currentUser;

这是一个更复杂的例子。

TMLLocalizedAttributedString(
    @"[bold: {user}] has completed [bold: {count || mile}] on {user| his, her} last run.",
    @{
        @"user": friend,
        @"count": @4.2,
        @"link": @{@"color": @"blue"}
        @"bold": @{@"font":@{@"name": @"system", @"size": @12, @"type": @"bold"}}
    }
)

在英语中,这将呈现为:

"**Michael** has completed **4.2 miles** on his last run."

翻译成俄语后为:

"[link: {user}] {user | пробежал, пробежала} [bold: {count || милю, мили, миль}] в своем последнем забеге."

将呈现为:

"**Michael** пробежал **4.2 мили** в своем последнем забеге."

运行时切换地区

可以指示 TMLKit 在运行时更改地区。您是否希望在您的应用程序的发布版本中提供该功能,取决于您对 Apple 的规则和指南的解释。然而,当测试应用程序和启用 应用内翻译 模式时非常有用。

因此,TMLKit 附加了一个简单的语言选择器,您可以在应用程序中使用它。要程序化打开语言选择器,使用以下代码:

TMLPresentLanguagePicker();

要程序化更改当前地区:

TMLChangeLocale(@"ru"); // will change to Russian locale

这里有一件事需要注意——除非您已将所有翻译数据与应用程序捆绑在一起,否则您的应用程序可能没有为新地区可用本地的翻译数据。在这种情况下,TMLKit 将在后台下载所有必需的数据。这意味着地区更改的实际操作可能会推迟。如果您想,例如,在数据下载时显示进度指示器,可以使用以下调用:

TML *tml = [TML sharedInstance];
MBProgressHUD *hud = nil;

if ([tml hasLocalTranslationsForLocale:newLocale] == NO) {
    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
    hud.labelText = TMLLocalizedString(@"Switching language...");
    [hud show:YES];
}

[tml changeLocale:newLocale
  completionBlock:^(BOOL success) {
    [hud hide:YES];
  }];

这是 TMLLanguageSelectorViewController 的大致工作原理。

当然这表示所有已经展示了本地化字符串的视图都需要更新为新的本地化字符串。请参阅 [可重用的本地化字符串] 以获取详细信息...

可重用的本地化字符串

如果您想提供动态语言切换(请参阅 [运行时切换地区]),则应用程序需要能够更新所有已经使用本地化字符串的对象。考虑一个基本的 UILabel:

UILabel *label = self.titleLabel;
label.attributedText = TMLLocalizedAttributedString(@"[bold:Title]: {title}", @{@"title": @"My Title"});

当您的应用程序更改区域设置时,此标签需要使用对应新区域设置的本地化字符串进行更新。为了方便这些更新,TMLKit 提供了额外的宏:TMLLocalizedStringWithReuseIdentifier() 和 TMLLocalizedAttributedStringWithReuseIdentifier()

基本用法

TMLLocalizedStringWithReuseIdenitifer(string, reuseIdentifier, ...);
TMLLocalizedAttributedStringWithReuseIdenitifer(string, reuseIdentifier, ...);

与已熟悉的 TMLLocalizedString() 和 TMLLocalizedAttributedString() 宏相同,但它们接受一个必需的第二个参数——标识符。

因此,上面的 UILabel 示例变为

UILabel *label = self.titleLabel;
label.attributedText = TMLLocalizedAttributedStringWithReuseIdenitifer(@"[bold:Title]: {title}", @{@"title": @"My Title"}, @"titleLabel");

这导致 TMLKit 注册调用者(调用时上下文中的 'self'),当需要更新本地化字符串时,如果定义了该调用者的 updateTMLLocalizedStringWithInfo:forReuseIdentifier: 方法,它将调用该方法。

TMLReusableLocalization 协议中将此方法定义为可选。TMLKit 通过遵守此协议扩展 NSObject。

@protocol TMLReusableLocalization <NSObject>
@optional
- (void)updateTMLLocalizedStringWithInfo:(NSDictionary *)info forReuseIdentifier:(NSString *)reuseIdentifier;
@end

我们稍后会回到这个话题,但首先让我们完成我们的 UILabel 示例。在同一个类中,我们可以这样定义

- (void)updateTMLLocalizedStringWithInfo:(NSDictionary *)info forReuseIdentifier:(NSString *)reuseIdentifier {
  if ([reuseIdentifier isEqualToString:@"titleLabel"] == YES) {
    self.titleLabel.attributedText = info[TMLLocalizedStringInfoKey];
  }
  else {
    [super updateTMLLocalizedStringWithInfo:info forReuseIdentifier:reuseIdentifier];
  }
}

请注意,传递给 updateTMLLocalizedStringWithInfo:forReuseIdentifier: 的 info 对象已经包含新的本地化字符串;它还包含最初构建它时使用的数据。这意味着 TMLLocalizedStringWithReuseIdentifier() 会捕获 (strongly) 它的所有参数。值得注意的是,这些数据与发送对象相关联,TMLKit 会弱捕获发送对象。因此,一旦发送对象被释放,所有捕获的本地化数据也将随之释放。

此外,请注意我们正在调用 super 的实现...

TMLKit 为更新可重用的本地化字符串定义了默认行为——它将 reuseIdentifier 视为发送对象的关键路径。如果发送对象响应 -[NSObject valueForKeyPath:],它将尝试通过 -[NSObject setValue:forKeyPath:] 更新值。这就是 TMLKit 处理自动 NIB 本地化的方式。

让我们简化我们的 UILabel 示例

@interface MyViewController : UIViewController
@property (strong, nonatomic) UILabel *titleLabel;
@end

@implementation MyViewController
- (void)viewDidLoad {
  [super viewDidLoad];
  UIlabel *titleLabel = [[UILabel alloc] init];
  titleLabel.attributedText = TMLLocalizedAttributedStringWithReuseIdenitifer(@"[bold:Title]: {title}", @{@"title": @"My Title"}, @"titleLabel.attributedText");
  self.titleLabel = titleLabel;
}
@end

您只需这样做,TMLKit 的默认实现将简单地调用控制器上的 setValue:forKeyPath:,使用 "titleLabel.attributedText" 作为 keyPath。

假设您对 TMLKit 捕获用于创建本地化字符串的数据感到不舒服

@implementation MyViewController
- (void)viewDidLoad {
  [super viewDidLoad];
  UILabel *titleLabel = [[UIlabel alloc] init];
  self.titleLabel = titleLabel;
  [self configureView];
  [[TML sharedInstance] registerObjectWithReusableLocalizedStrings:self];
}

- (void)configureView {
  self.titleLabel.attributedText = TMLLocalizedAttributedString(@"[bold:Title]: {title}", @{@"title": @"My Title"});
}

- (void)updateReusableTMLStrings {
  [super updateReusableTMLStrings];
  [self configureView];
}
@end

首先——请注意我们使用的是 TMLLocalizedAttributedString(),而不是 TMLLocalizedAttributedStringWithReuseIdentifier()——因此,我们没有在此处捕获任何本地化数据。其次——我们通过 TML 注册控制器对象 via registerObjectWithReusableLocalizedStrings:. 当更新本地化字符串的时间来临时——TML 将调用此控制器的 updateReusableTMLStrings 方法。我们通过调用 configureView 进行响应,该调用器重新本地化标签。

应用内翻译器

TMLKit 支持从应用内部直接翻译字符串。通过执行一个特殊且可定制的手势,TMLKit 将切换到实时翻译模式。这需要 TMLKit 首先通过 TranslationExchange 服务验证翻译者。要程序化启用实时翻译模式

TMLActivateTranslation(YES); // NO argument disables

此功能默认启用,但可以通过 TMLConfiguration 禁用。

TMLSharedConfiguration().disallowTranslation = YES;

一旦启用 应用内翻译 模式,长按屏幕上的任何本地化字符串——TMLKit 将为该特定字符串弹出一个翻译器界面。从那里您可以添加/删除以及管理该字符串的翻译。

您也可以通过编程方式打开此接口

TMLPresentTranslatorForKey([TMLTranslationKey generateKeyForLabel:@"<localized string>" description:@"<optional comment>"]);

链接

版权和许可

版权(c)2017 TranslationExchange.com

在此特此授权,免费提供给任何获得此软件及其相关文档文件(“软件”)副本的人士使用软件,不受限制地处理软件,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或销售软件的副本,并允许向获得软件的人士提供上述权利,前提是遵守以下条件

上述版权声明和本许可声明应包括在软件的所有副本或实质性部分中。

软件按“现状”提供,不提供任何形式的保证,无论是明示的、暗示的还是其他的,包括但不限于适销性、适用于特定用途和侵权不担保。在任何情况下,作者或版权所有者对任何索赔、损害或其他责任(无论是基于合同、侵权或其他),无论是因软件本身、软件的使用或其他与软件相关的交易而产生或源于此,均不承担责任。