iOS国际化——通过脚本使storyboard翻译自增

 一. 针对两种文件的国际化处理

  代码中即.m文件的国际化

  首先在你需要进行国际化处理的字符串外面加一层NSLocalizedString,注意中文也是可以的哦

textfield.text = [NSString stringWithFormat:NSLocalizedString(@"使用帮助", nil)];

  NSLocalizedString是一个定义在NSBundle.h中的宏,其用途是寻找当前系统语言对应的Localizable.strings文件中的某个key的值。
  第一个参数是key的名字,第二个参数是对这个“键值对”的注释,在用genstrings工具生成Localizable.strings文件时会自动加上去。

  当我们把所有的.m文件都修改好了,就可以动用genstrings工具了

  1. 启动终端,进入工程所在目录。
  2. 新建两个目录,推荐放在资源目录下。
    目录名会作用到Localizable.strings文件对应的语言,不能写错了。这里zh-Hans指简体中文,注意不能用zh.lproj表示。

mkdir zh-Hans.lprojmkdir en.lproj

  3. 生成Localizable.strings文件

genstrings -o zh-Hans.lproj *.mgenstrings -o en.lproj *.m

  -o <文件夹>,指定生成的Localizable.strings文件放置的目录。
  *.m,扫描所有的.m文件。这里支持的文件还包括.h, .java等。

  如果你认为这样就结束了,那就too naive了,实际上,上面genstrings指令只能是该目录下的文件遍历,但不能实现递归遍历,这样在一些大工程里面明显不能满足需求,于是在工程里更普遍的用法是

find ./ -name *.m | xargs genstrings -o en.lproj

  这是传说中shell组合指令find+xargs,find ./ -name *.m 会递归所有.m文件并存在一个数组中,这个数组经由pipe传递给了下一个指令,而xargs会将收到的数组一一分割,并交给genstrings执行。

这样我们就可以得到如下的文件结构

 

  storyboard的国际化处理

  看过文档或者其他介绍的都知道storyboard的国际化非常简单,这里简单介绍一下

  1 PROJECT -> Info -> Localizations 在这里添加你想增加的语种

   

  2 将storyboard的inspector栏目的localization项勾上后(图左),project的file navigator那里会多出几个文件(图右)

      

 2 国际化后续优化

  不足之处

  到了这里其实关于iOS的国际化已经算是完成了,但你可能也发现一个问题了,上面两种方法全都是一次性生成文件,但我们的项目往往会不断地更新。重新照上面流程跑,生成的string文件会将之前翻译好的文件覆盖掉。

  代码的国际化还好,毕竟一般面对新需求我们都会新建新的类和文件,只要把新代码文件脱到一个文件夹下,执行genstrings便可以得到一份对应的本地化文件,然后拿给翻译人员,最后将翻译好的键值对追加在初始的localizable.string文件尾部即可。

  而storyboard的国际化便不能如此了,当然新建一个storyboard也是可以的,但如果是小改动我们一般还是会在原storyboard那里添加控件,这时候,原storyboard新增控件的国际化是我们今天要解决的重点。

  解决方案

  仔细观察storyboard的翻译文件,你会发现这里面也是一个个键值对,key是控件ID+状态,值就是显示文本。假设原来我们就有翻译文件A,添加控件后,我们再执行一次国际化指令,生成文件B,我们拿A和B对比,把B中多出来的键值对插入A文件的尾部,将A中有而B中没有的键值对删掉(即控件被删除),这样我们就算是更新了storyboard的翻译文件了。而这一步操作我们可以借助脚本文件来实现,XCode的Run Script也提供了脚本支持,可以让我们在Build后执行脚本。

  脚本代码

#!/usr/bin/env python
# encoding: utf-8

"""
untitled.py

Created by linyu on 2015-02-13.
Copyright (c) 2015 __MyCompanyName__. All rights reserved.
"""

import imp
import sys
import os
import glob
import string
import re
import time

imp.reload(sys)
sys.setdefaultencoding(‘utf-8‘) #设置默认编码,只能是utf-8,下面\u4e00-\u9fa5要求的

KSourceFile = ‘Base.lproj/*.storyboard‘

KTargetFile = ‘*.lproj/*.strings‘  

KGenerateStringsFile = ‘TempfileOfStoryboardNew.strings‘

ColonRegex = ur‘["](.*?)["]‘

KeyParamRegex = ur‘["](.*?)["](\s*)=(\s*)["](.*?)["];‘

AnotationRegexPrefix = ur‘/(.*?)/‘

def getCharaset(string_txt):
	filedata = bytearray(string_txt[:4])
	if len(filedata) < 4 :
		return 0
	if  (filedata[0] == 0xEF) and (filedata[1] == 0xBB) and (filedata[2] == 0xBF):
		print ‘utf-8‘
		return 1
	elif (filedata[0] == 0xFF) and (filedata[1] == 0xFE) and (filedata[2] == 0x00) and (filedata[3] == 0x00):
		print ‘utf-32/UCS-4,little endian‘
		return 3
	elif (filedata[0] == 0x00) and (filedata[1] == 0x00) and (filedata[2] == 0xFE) and (filedata[3] == 0xFF):
		print ‘utf-32/UCS-4,big endian‘
		return 3
	elif (filedata[0] == 0xFE) and (filedata[1] == 0xFF):
		print ‘utf-16/UCS-2,little endian‘
		return 2
	elif (filedata[0] == 0xFF) and (filedata[1] == 0xFE):
		print ‘utf-16/UCS-2,big endian‘
		return 2
	else:
		print ‘can not recognize!‘
		return 0

def decoder(string_txt):
	var  = getCharaset(string_txt)
	if var == 1:
		return string_txt.decode("utf-8")
	elif var == 2:
		return string_txt.decode("utf-16")
	elif var == 3:
		return string_txt.decode("utf-32")
	else:
		return string_txt

def constructAnotationRegex(str):
	return AnotationRegexPrefix + ‘\n‘ + str

def getAnotationOfString(string_txt,suffix):
	anotationRegex = constructAnotationRegex(suffix)
	anotationMatch = re.search(anotationRegex,string_txt)
	anotationString = ‘‘
	if anotationMatch:
		match = re.search(AnotationRegexPrefix,anotationMatch.group(0))
		if match:
			anotationString = match.group(0)
	return anotationString

def compareWithFilePath(newStringPath,originalStringPath):
	#read newStringfile
	nspf=open(newStringPath,"r")
	#newString_txt =  str(nspf.read(5000000)).decode("utf-16")
	newString_txt =  decoder(str(nspf.read(5000000)))
	nspf.close()
	newString_dic = {}
	anotation_dic = {}
	for stfmatch in re.finditer(KeyParamRegex , newString_txt):
		linestr = stfmatch.group(0)
		anotationString = getAnotationOfString(newString_txt,linestr)
		linematchs = re.findall(ColonRegex, linestr)
		if len(linematchs) == 2:
			leftvalue = linematchs[0]
			rightvalue = linematchs[1]
			newString_dic[leftvalue] = rightvalue
			anotation_dic[leftvalue] = anotationString
	#read originalStringfile
	ospf=open(originalStringPath,"r")
	originalString_txt =  decoder(str(ospf.read(5000000)))
	ospf.close()
	originalString_dic = {}
	for stfmatch in re.finditer(KeyParamRegex , originalString_txt):
		linestr = stfmatch.group(0)
		linematchs = re.findall(ColonRegex, linestr)
		if len(linematchs) == 2:
			leftvalue = linematchs[0]
			rightvalue = linematchs[1]
			originalString_dic[leftvalue] = rightvalue
	#compare and remove the useless param in original string
	for key in originalString_dic:
		if(key not in newString_dic):
			keystr = ‘"%s"‘%key
			replacestr = ‘//‘+keystr
			match = re.search(replacestr , originalString_txt)
			if match is None:
				originalString_txt = originalString_txt.replace(keystr,replacestr)
	#compare and add new param to original string
	executeOnce = 1
	for key in newString_dic:
		values = (key, newString_dic[key])
		if(key not in originalString_dic):
			newline = ‘‘
			if executeOnce == 1:
				timestamp = time.strftime(‘%Y-%m-%d %H:%M:%S‘,time.localtime(time.time()))
				newline = ‘\n//##################################################################################\n‘
				newline	+=‘//#           AutoGenStrings            ‘+timestamp+‘\n‘
				newline	+=‘//##################################################################################\n‘
				executeOnce = 0
			newline += ‘\n‘+anotation_dic[key]
			newline += ‘\n"%s" = "%s";\n‘%values
			originalString_txt += newline
	#write into origial file
	sbfw=open(originalStringPath,"w")
	sbfw.write(originalString_txt)
	sbfw.close()

def extractFileName(file_path):
	seg = file_path.split(‘/‘)
	lastindex = len(seg) - 1
	return seg[lastindex]

def extractFilePrefix(file_path):
	seg = file_path.split(‘/‘)
	lastindex = len(seg) - 1
	prefix =  seg[lastindex].split(‘.‘)[0]
	return prefix

def generateStoryboardStringsfile(storyboard_path,tempstrings_path):
	cmdstring = ‘ibtool ‘+storyboard_path+‘ --generate-strings-file ‘+tempstrings_path
	if os.system(cmdstring) == 0:
		return 1

def main():
	filePath = sys.argv[1]
	sourceFilePath = filePath + ‘/‘ + KSourceFile
	sourceFile_list = glob.glob(sourceFilePath)
	if len(sourceFile_list) == 0:
		print ‘error dictionary,you should choose the dic upper the Base.lproj‘
		return
	targetFilePath = filePath + ‘/‘ + KTargetFile
	targetFile_list = glob.glob(targetFilePath)
	tempFile_Path = filePath + ‘/‘ + KGenerateStringsFile
	if len(targetFile_list) == 0:
		print ‘error framework , no .lproj dic was found‘
		return
	for sourcePath in sourceFile_list:
		sourceprefix = extractFilePrefix(sourcePath)
		sourcename = extractFileName(sourcePath)
		print ‘init with %s‘%sourcename
		if generateStoryboardStringsfile(sourcePath,tempFile_Path) == 1:
			print ‘- - genstrings %s successfully‘%sourcename
			for targetPath in targetFile_list:
				targetprefix = extractFilePrefix(targetPath)
				targetname = extractFileName(targetPath)
				if cmp(sourceprefix,targetprefix) == 0:
					print ‘- - dealing with %s‘%targetPath
					compareWithFilePath(tempFile_Path,targetPath)
			print ‘finish with %s‘%sourcename
			os.remove(tempFile_Path)
		else:
			print ‘- - genstrings %s error‘%sourcename

if __name__ == ‘__main__‘:
	main()

  

  1 新建一个文件夹RunScript,存放python脚本,放在工程文件的目录下,如图:

  

  为了直观可以顺便把该文件夹拖入工程中(不做也可以)

  

  2 Target->Build Phases->New Run Script Phase,在shell里面写入下面指令

  python ${SRCROOT}/${TARGET_NAME}/RunScript/AutoGenStrings.py ${SRCROOT}/${TARGET_NAME}

  

  3 在Storyboard中添加一个textfield控件,再Bulid一下,成功后我们打开翻译文件,发增加了如下文本

  

  4 我们没有必要每次Build都运行一次脚本,所以当你不需要的时候,可以在步骤2选上Run Script Only when installing,这样他就不会跑脚本,具体原因有兴趣的可以自行百度一下。

  以下是相关程序的GitHub地址:

  https://github.com/linyu92/MyGitHub

  

时间: 2024-12-21 11:25:46

iOS国际化——通过脚本使storyboard翻译自增的相关文章

详述iOS国际化

在真正将国际化实践前,只知道通过NSLocalizedString方法将相应语言的字符串加载进来即可.但最近公司项目的新需求增加英文版本,并支持应用内无死角切换~,这才跳过各种坑实现了应用内切换语言,并记录至此. 环境 系统环境: iOS7 - iOS9 开发环境: Swift2 & Xcode7 DEMO: LocalDemo 这个Demo的功能主要是切换语言后相应的界面文字&图片以及搜索引擎都会随语言变化.我们会围绕这个DEMO进行讲解,读者可以先下载这个Demo运行看下效果再往下 i

iOS国际化

iOS国际化的大致步骤如下: #1 在最新的Xcode中没有InfoPlist.strings & Localization.strings文件, 所以我们要自己建立这两个文件 #2 选择刚创建的Localization.strings文件, 点击右边的Localize按钮 #3 一般默认有Base和English选项, #4 选择Base, 然后在右边这个位置可以根据需要把English选上 #5 在Project中Localizations栏位添加要做国际化的语言 比如我们添加中文, 如果只

iOS 国际化多语言设置 xcode7

iOS 国际化多语言设置 方式一: 1. 在storyboard中创建好UI,然后在 project 里面  Localizables 栏目里面,添加你需要的语言:默认是Englist; 比如这里我添加了 简体中文 2.添加完之后,原来的Main.storyboard 就会多了一个国际化的语言配置文件 3. 点击 Main.strings 文件,就可以在这里配置中文的语言:里面自动添加现在storyboard上面创建好的UI控件的代码,直接在这里修改即可:此时sotyboard上设计的理解为 英

iOS framework配置脚本

# Sets the target folders and the final framework product. FMK_NAME=HovnVoipEngine FMK_VERSION=1.0 # Install dir will be the final output to the framework. # The following line create it in the root folder of the current project. INSTALL_DIR=${SRCROO

[IOS]《A Swift Tour》翻译(一)

以下翻译内容为原创,转载请注明: 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/3768936.html 碎碎念... Swift是苹果在WWDC刚发布的新语言,本人也没学过,现在看苹果官方文档一边翻译一边学习,再加上英语水平和对编程理解很有限,有错误的地方请大家指出,翻译只供参考,建议阅读苹果Swift官方的文档 Swift 之旅 按照传统,在开始学习一门新的语言时写的第一个程序应该是在屏幕上打印"Hello, World",这个可以

iOS国际化时遇到的错误:read failed: the data couldn&amp;#39;t be read because it isn&amp;#39;t in the correct format.

事实上这个错误非常easy,就是当我们在国际化的时候,写key,写着写着就忘了加 ";" 所以查看一下自己的Localization文件就能够了 iOS国际化时遇到的错误:read failed: the data couldn't be read because it isn't in the correct format.

ios学习记录 day44 UI20 Storyboard 高级可视化编程 + 单例

可视化编程 : 5.0之前 xib  5.0之后 Storyboard 注意事项:1.在AppDelegate中不用代码初始化一个window   2.在general  设置 Main Interface(创建的时候自动设置的) weak可以指向 但是没有拥有权   strong有拥有权 单例特性: 1.唯一性  创建的对象永远指向同一块内存地址     2.不释放   从打开程序到后台关闭 对象都不释放 单例安全:加锁@synchronized  防止同一块内存被创建两次,破坏唯一性 //单

iOS国际化时遇到的错误:read failed: the data couldn&#39;t be read because it isn&#39;t in the correct format.

其实这个错误很简单,就是当我们在国际化的时候,写key,写着写着就忘了加 ";" 所以查看一下自己的Localization文件就可以了 iOS国际化时遇到的错误:read failed: the data couldn't be read because it isn't in the correct format.

iOS 国际化 NSLocalizedString

iOS 国际化.根据系统不同的语言自动切换. 首先.选择项目 Add new file -->iOS -->Resource -->Strings File  . 命名为Localizable.strings 打开Localizable.strings.它的格式是这样的 "KEY" = "CONTENT"; 请按这种格式来输写,比如我这里是这样定义的 "testKey" = "qingjoin_english"