软件多语言文案脚本自动化方案

开发高效提速系列目录

  1. 软件多语言文案脚本自动化方案

博客创建时间:2023.05.03
博客更新时间:2023.05.03

以Android studio gradle=7.5,SDKVersion 32来分析讲解。如图文和网上其他资料不一致,可能是别的资料版本较低而已。


背景

在海外项目中多语言的支持是很重要的一部分。在我们的项目中常常需要支持简中、繁体、英、日、韩、西班牙语等十多种语言,当我们进行文案的新增、修改、删除时,需要在软件项目多语言文案资源文件中进行文案Key和文案内容对应的复制粘贴,非常耗时耗力且容易出现疲劳性错误。

目前关于多语言文案的管理,有部分公司的项目管理可能还停留在如下流程中:

  1. 文案由开发在项目资源文件中进行维护(如Android的strings.xml、IOS的Localizable.strings、PC云端的XXX.ini ),当需要进行文案翻译时,将文案文件语言.xml文件给到产品经理
    在这里插入图片描述

  2. 由产品经理经过其他操作对文案进行翻译,提供多语言翻译后的excel文档给到软件开发,此时文案Key和文案内容失去关联关系
    在这里插入图片描述

  3. 软件开发根据给到的翻译内容查找到对应的文案Key,然后复制到对应的资源文件中进行文案的更新操作

该流程实现过程中,会出现可能的几点问题:

  1. 在版本开发迭代期间,频繁的文案新增修改,需要开发人员频繁手动对齐文案和Key的关系,费时费力
  2. 每一中语言都需要将操作重复进行一遍,语言种类越多耗时越久且手动复制粘贴中容易出现疲劳性错误

目标

为了方便对文案资源进行统一管理,方便产品、开发、测试对文案内容的统一对齐,应该在已有的翻译excel文档的基础上完善文案,对每条文案新增对应的文案Key,同时采用python脚本方法,实现脚本自动化程序更新文案资源,达到提升文案管理和使用效率提升。该方案可以支持Android、IOS、PC云端项目的。

整体方案

该方案的主要思路是:

  1. 创建好统一管理Android、IOS或PC 云端项目的文案管理excel文件
  2. 根据文案资源Excel文件进行python脚本的编写,达到执行脚本时能生成不同格式的文案资源文件
  3. 当文案资源有更新时,在excel文档中进行更新,执行脚本程序完成文案资源的更新
    在这里插入图片描述

根据思路我将其细分为如下几个细分步骤:

  1. 建立标准化文案管理文件。一般是用excel文件管理
  2. 编写Python脚本开发
  3. Python文件管理与程序触发执行
  4. 项目组内人员职责分配

1. 创建文案资源文件

多语言文案可以放在某个excel文件中进行管理,一般使用公司的办公软件如钉钉或飞书软件中进行管理。文案资源的格式可以一般同产品沟通协商共同制作,符合自己公司产品的需求即可,我这里给出移动端文案sheet和PC 云端sheet模板样式,云端和移动端因文案差异太大,建议不用放在一起。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意:多语言的文案翻译根据软件的实际需求进行使用,如软件不支持某种语言如"土耳其语",将其对应列空着即可。

2. python脚本开发

python脚本代码如下

# coding=utf-8
import importlib
import re
import sys
import os
import xlrd2

importlib.reload(sys)
# isAndroid = True
ANDROID = "Android"
IOS = "IOS"
PC = "PC"
# 配置文件修改区 START *****/
# 平台类型 Android、IOS、PC 区分大小写
platformType = "Android"

# 项目多语言资源文件
excelPath = os.path.expanduser('~/Downloads/多语言文案.xlsx')
# 输出路径
outputPath = "D:/Ken.Luo/res/"

# 选择需要导出文案的sheet名
sheetName = "App文案"
# 配置文件修改区 END *****/


# 导出文案的类型
# entryType = "Android"
commentColNum = 4
typeColNum = 2
keyColNum = 2
colToStringsMap = None


# 切换到iOS
def switchToAndroid():
    global keyColNum, colToStringsMap
    keyColNum = 2
    # 对应关系元组Android (列数 : 多语言文件)
    colToStringsMap = [(7, "values/strings.xml"),
                       (5, "values-zh/strings.xml"),
                       (6, "values-zh-TW/strings.xml"),
                       (8, "values-de/strings.xml"),
                       (9, "values-fr/strings.xml"),
                       (10, "values-es/strings.xml"),
                       (11, "values-ja/strings.xml"),
                       (12, "values-ko/strings.xml"),
                       (13, "values-ar/strings.xml"),
                       (14, "values-it/strings.xml"),
                       (15, "values-pt/strings.xml"),
                       (16, "values-tr/strings.xml"),
                       (17, "values-ru/strings.xml")]


# 切换到iOS
def switchToIOS():
    global keyColNum, colToStringsMap
    keyColNum = 3
    colToStringsMap = [(7, "values/Localizable.strings"),
                       (5, "values-zh/Localizable.strings"),
                       (6, "values-zh-TW/Localizable.strings"),
                       (8, "values-de/Localizable.strings"),
                       (9, "values-fr/Localizable.strings"),
                       (10, "values-es/Localizable.strings"),
                       (11, "values-ja/Localizable.strings"),
                       (12, "values-ko/Localizable.strings"),
                       (13, "values-ar/Localizable.strings"),
                       (14, "values-it/Localizable.strings"),
                       (15, "values-pt/Localizable.strings"),
                       (16, "values-tr/Localizable.strings"),
                       (17, "values-ru/Localizable.strings")]

def switchToPC():
    global keyColNum, colToStringsMap
    keyColNum = 2
    colToStringsMap = [(7, "values/strings.ini"),
                       (5, "values-zh/strings.ini"),
                       (6, "values-zh-TW/strings.ini"),
                       (8, "values-de/strings.ini"),
                       (9, "values-fr/strings.ini"),
                       (10, "values-es/strings.ini"),
                       (11, "values-ja/strings.ini"),
                       (12, "values-ko/strings.ini"),
                       (13, "values-ar/strings.ini"),
                       (14, "values-it/strings.ini"),
                       (15, "values-pt/strings.ini"),
                       (16, "values-tr/strings.ini"),
                       (17, "values-ru/strings.ini")]



# ******* #

def formatValue(originalStr):
    ''' 格式化字符串成为app 程序需要的样子'''
    print("originalStr:" + originalStr)

    # excel中的换行符换成 \n转义字符, 文案首尾的换行符认为是翻译人员不小心按出来的,在此去掉
    originalStr = re.sub(r'\n+$', "", originalStr, re.S)
    originalStr = re.sub(r'^\n+', "", originalStr, re.S)
    originalStr = re.sub(r'\n', "\\\\n", originalStr, re.S)
    # 安卓需要对&进行处理
    if platformType is ANDROID:
        #  不处理,待后续直接替换成空格
        if originalStr != ' ':
            originalStr = re.sub(r'&', "&", originalStr, re.S)

    print("")
    return originalStr


### 写多语言文件函数 ###
def write_header(strFile):
    if platformType is ANDROID:
        strFile.write("""<?xml version="1.0" encoding="utf-8" ?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">\n""")


def write_trailer(strFile):
    if platformType is ANDROID:
        strFile.write("\n</resources>")


def write_comment(strFile, comment):
    '''生成注释文案'''
    if comment != "" and comment != None:
        if platformType is ANDROID:
            strFile.write("\t<!-- " + str(comment) + "-->\n")
        elif platformType is IOS:
            strFile.write("/** " + str(comment) + " */\n")
        elif platformType is PC:
            strFile.write("# " + str(comment) + " \n")


def write_key_value(strFile, key, value, defaultValue):
    # iOS对于value是空使用defaultValue
    if value == "&nbsp;":
        value = " "
    if not typeColNum is ANDROID and (value is None or value == ""):
        value = defaultValue

    if key is None or key == "" or value is None or value == "":
        return
    # 处理一个文案多个key的情况
    keyColValues = key.splitlines()
    for keyColValue in keyColValues:
        if platformType is ANDROID:
            strFile.write("\t<string name=\"" + keyColValue.strip() + "\">" + value.strip() + "</string>\n")
        elif platformType is IOS:
            strFile.write("\"" + keyColValue.strip() + "\" = \"" + value.strip() + "\";\n")
        elif platformType is PC:
            strFile.write(keyColValue.strip() + " = \"" + value.strip() + "\";\n")


# ******* #
workbook = xlrd2.open_workbook(excelPath)
table = workbook.sheet_by_name(sheetName)


def writeToFiles():
    nrows = table.nrows
    # 打开文件
    colToFileMap = []

    for (col, fileName) in colToStringsMap:
        paths = os.path.split(fileName)
        print("paths 路径", paths)
        if len(paths) > 1:
            pathDir = outputPath + paths[0]
            print("dir 路径", pathDir)

            # 输出目录文件夹不存在就创建
            if not (os.path.exists(pathDir)):
                os.makedirs(pathDir)

        file = open(outputPath + fileName, "w+", 1024, "utf-8")
        colToFileMap.append((col, file))
        write_header(file)

    # 导出文案
    for i in range(2, nrows):
        print(table)
        comment = table.cell(i, commentColNum).value
        typeColValue = table.cell(i, typeColNum).value
        keyColValue = table.cell(i, keyColNum).value
        defaultValue = ""

        # 将一行的各列写入对应的多语言文件
        for (col, strFile) in colToFileMap:
            value = table.cell(i, col).value
            value = formatValue(value)
            if col == colToFileMap[0][0]:
                defaultValue = value

            write_comment(strFile, comment)
            # if typeColValue.find(entryType) >= 0:
            write_key_value(strFile, keyColValue, value, defaultValue)

    # 关闭文件
    for (_, file) in colToFileMap:
        write_trailer(file)
        file.close()


if __name__ == '__main__':
    if platformType is ANDROID:
        switchToAndroid()
        writeToFiles()
    elif platformType is IOS:
        switchToIOS()
        writeToFiles()
    elif platformType is PC:
        switchToPC()
        writeToFiles()

3. Python脚本执行与管理

Python脚本的执行需要在电脑中进行触发程序执行,需要电脑有python运行环境,我使用的是Pycharm idea软件,一键安装即可用,和Android studio使用是一样的。后面会专门讲解PyCharm的使用操作。

参数配置
当执行软件安装好后,需要进行部分参数的配置,配置完参数后即可进行程序的执行,生成对应的资源文件。

ANDROID = "Android"
IOS = "IOS"
PC = "PC"
# 配置文件修改区 START *****/
# 平台类型 Android、IOS、PC 区分大小写
platformType = "PC"

# 项目多语言资源文件
excelPath = os.path.expanduser('~/Downloads/Remo文案汇总.xlsx')
# 输出路径
outputPath = "D:/Ken.Luo/res88888/"

# 选择需要导出文案的sheet名
sheetName = "APP文案"
# 配置文件修改区 END *****/

主要修改的是参数为

  1. platformType:资源使用的平台,目前定义有Android、IOS、PC云端
  2. excelPath :excel资源文件的目录地址
  3. outputPath:脚本执行后的生成的输出位置
  4. sheetName :APP和PC云端的文案资源我设计是分开放的,所以是两个sheet,需要执行程序时手动选择

执行效果
以Android举例,多语言文案资源是放置在res/value/strings.xml文件中下的。现在我们预计通过执行python脚本程序,实现文案内容自动更新到各语言的strings.xml中。
在这里插入图片描述
脚本管理
python脚本是为了减少开发进行文案更新时提效的,当脚本程序开发完毕后考虑后期可能有优化,需要对齐进行持久化管理,所以首先想到的是使用gitee进行管理。https://gitee.com/luofaxin/LanguageRes.git

4. 人员职责分配

在多语言文案管理中,不同角色有自己的任务分配
在这里插入图片描述

PyCharm使用说明

在python脚本准备好后,需要通过某种方式实现程序的触发,第一阶段先考虑手动操作pycharm程序执行脚本程序,后期也可以考虑在服务器上进行脚本的自触发。

1. PyCharm下载

手动触发方式需要使用者安装Pycharm 软件,用于执行文案脚本程序。https://www.jetbrains.com/pycharm/download/#section=windows
在这里插入图片描述

2. PyCharm安装配置

在这里插入图片描述

  1. 配置.py运行环境
    在这里插入图片描述
    在这里插入图片描述

  2. 配置脚本执行变量
    对于.py文件,需要动态的更改路径的配置
    在这里插入图片描述

  3. 执行脚本程序
    点击执行脚本程序,python脚本将会自动执行,将excel文件生成res/strings.xml文件
    在这里插入图片描述

3. 异常情况解决

  1. Python ModuleNotFoundError: No module named ‘xlrd‘ 异常
    解决方案
    在这里插入图片描述
    在这里插入图片描述

总结

希望这个python脚本自动化方案能帮助大家,多语言文案资源和pyhton脚本我都放到gitee仓库中了,可以下载使用。欢迎大家提出更好的优化建议。
仓库地址:https://gitee.com/luofaxin/LanguageRes.git


相关链接

  1. 软件多语言文案脚本自动化方案

扩展链接:

  1. 项目开发混淆从初识到理解
  2. 项目开发代码分支管理

博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值