大專案中網頁多語系的維護方式
也許在許多專案上我們所說的Multi Language僅有繁體中文、簡體中文和英文,但在比較大型且跨國的專案上,可能會涉及更多的語言,例如:日文、俄文、德文、法文、西班牙文…等等,通常這些軟體內容的翻譯,需要經過更專業的單位來進行,也許這個單位不僅僅需要具備有這些國家的語言能力,也要具備有相當的軟體知識,才能配合當地民情翻譯出正確的文字,這部分往往需要專業的翻譯單位來進行。
而專業的翻譯單位通常並不具有軟體的製作能力,所以在軟體上要如何快速地進行協同作業,就變成一項非常重要的工作,而在許多專案上,我們會讓翻譯單位透過Excel來提供各國語系的翻譯文字,我們則透過軟體進行轉換,將其轉換至軟體能快速讀取的格式,講白一點就是將Excel檔案轉換成XML格式,並提供其他軟體進行讀取。

整體流程會如上圖所示,在拿到一個翻譯社提供所有語系的Excel資料後,我們會進行轉換,將文字轉換成多個XML檔案,並打包成一個ZIP檔讓轉換者進行下載。
今天我們主要來分享上圖藍色部分的處理程序,也就是那一隻轉換程式的結構和做法,我們將轉換程式設計成網頁版本,藉此提升易用性,而轉換程式操作Flow大致如下:
- 使用者上傳檔案(限制僅能上傳Excel檔案)
- 給出ZIP下載連結(提供使用者下載所有語系的XML檔)
操作上非常簡單,僅有上述這兩個步驟,而程式設計上採用JSP架構其運作邏輯如下:
- 檢查上傳檔案的格式、容量及相關資訊
- 擷取Excel中第一張工作表(也可以依照工作表名稱擷取)
- 將Excel中第一列視為語系標題,並當作存檔名稱(例如:English.xml)
- 將剩下來的每一列轉換為該檔案的語系資料,並建立XML檔案
- 將所有建立好的XML檔案進行打包(ZIP)
- 更新頁面產生ZIP檔下載路徑
在該轉換程式中,另外有利用到下述的JAVA Library:
- Apache POI – 處理與解析Excel檔案
- DOM4J – 建立XML檔案
- Apache Commons – 處理檔案上傳
以下是轉換的程式碼:
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page import="java.io.File"%>
<%@ page import="java.text.*" %>
<%@ page import="java.util.*" %>
<%@ page import="java.util.Iterator"%>
<%@ page import="java.util.List"%>
<%@ page import="org.apache.commons.fileupload.*"%>
<%@ page import="org.apache.commons.io.FilenameUtils"%>
<%@ page import="java.util.zip.ZipEntry"%>
<%@ page import="java.util.zip.ZipOutputStream"%>
<%@ page import="java.io.FileInputStream"%>
<%@ page import="java.io.FileOutputStream"%>
<%@ page import="java.io.IOException"%>
<%@ page import="java.io.OutputStreamWriter"%>
<%@ page import="java.nio.charset.Charset"%>
<%@ page import="org.apache.poi.hssf.usermodel.HSSFRow"%>
<%@ page import="org.apache.poi.hssf.usermodel.HSSFSheet"%>
<%@ page import="org.apache.poi.hssf.usermodel.HSSFWorkbook"%>
<%@ page import="org.dom4j.io.OutputFormat"%>
<%@ page import="org.dom4j.io.XMLWriter"%>
<%@ page import="org.dom4j.Document"%>
<%@ page import="org.dom4j.DocumentHelper"%>
<%@ page import="org.dom4j.Element"%>
<%!
//允許上傳的檔案
String allowedFileTypes = ".xls";
//建立目錄
public void newFolder(String folderPath) {
try {
String filePath = folderPath;
filePath = filePath.toString();
java.io.File myFilePath = new java.io.File(filePath);
if (!myFilePath.exists()) {
myFilePath.mkdir();
}
}
catch(Exception e) {
System.out.println("建立目錄錯誤");
//e.printStackTrace();
}
}
// 轉換XLS為XML的主程式 ; 參數1.欲轉換的Excel工作表編號; 參數2.轉換的檔案路徑與檔名; 參數3.XML儲存的檔案路徑;
public static String convertSheet(int sheetNumber, String conversionFile, String conversionXMLFilePath) {
String convertStatus = "0"; // 輸出轉換狀態 ; 0 是失敗; 1是成功
String conversionXMLFileName = null; // XML檔名
String conversionXMLFile = null; // XML完整路徑與檔名
// 產生儲存XML檔案的資料夾
File file = new File(conversionXMLFilePath);
if(!file.exists()){
file.mkdirs();
}
// 開始讀取XLS檔案
HSSFWorkbook book = null;
try {
book = new HSSFWorkbook(new FileInputStream(conversionFile));
} catch (IOException e) {
System.out.println("IOException : " + e);
}
HSSFSheet sheet = book.getSheetAt(sheetNumber); // 打開對應編號的工作表
HSSFRow row = sheet.getRow(0);// 取得工作表的第一列資料
String cell;
int totalRows = sheet.getPhysicalNumberOfRows(); // 取得工作表中所有的列數
int totalCol = row.getPhysicalNumberOfCells(); // 取的工作表中所有的欄數
// 開始建立XML檔並將XLS內容建入
for (int j = 1; j < totalCol; j++){
Document document = DocumentHelper.createDocument();
Element root = document.addElement("root");
for (int i = 0; i < totalRows; i++){
row = sheet.getRow(i);
try {
cell = row.getCell(j).toString();
if(i==0) {
conversionXMLFileName = cell;
conversionXMLFile = conversionXMLFilePath + conversionXMLFileName + ".xml";
}else {
root.addElement("row_" + (i+1)).addCDATA(cell);
/*
if(sheetNumber == 0) {
root.addElement("tag_" + (i-1), cell);
}else {
root.addElement(xmlKeyboardTitle[(i-1)], cell);
}
*/
}
} catch (NullPointerException e) {
break;
}
}
File storedFile = new File(conversionXMLFile);
if(storedFile.exists())
storedFile.delete();
FileOutputStream fos = null;
OutputStreamWriter osw = null;
XMLWriter writer = null;
try {
storedFile.createNewFile();
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("utf-8");
fos = new FileOutputStream(storedFile);
osw = new OutputStreamWriter(fos, Charset.forName("utf-8"));
writer = new XMLWriter(osw, format);
writer.write(document);
} catch (IOException e) {
System.out.println("IOException : " + e);
} finally {
try {
if(writer != null) writer.close();
if(osw != null) osw.close();
if(fos != null) fos.close();
convertStatus = "1";
} catch (IOException e) {
System.out.println("IOException : " + e);
}
}
}
return convertStatus; // 回覆轉換狀態
}
List<String> filesListInDir = new ArrayList<String>();
public void zipDirectory(File dir, String zipDirName) {
filesListInDir = new ArrayList<String>();
try {
populateFilesList(dir);
//now zip files one by one
//create ZipOutputStream to write to the zip file
FileOutputStream fos = new FileOutputStream(zipDirName);
ZipOutputStream zos = new ZipOutputStream(fos);
for(String filePath : filesListInDir){
System.out.println("Zipping "+filePath);
//for ZipEntry we need to keep only relative file path, so we used substring on absolute path
ZipEntry ze = new ZipEntry(filePath.substring(dir.getAbsolutePath().length()+1, filePath.length()));
zos.putNextEntry(ze);
//read the file and write to ZipOutputStream
FileInputStream fis = new FileInputStream(filePath);
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) > 0) {
zos.write(buffer, 0, len);
}
zos.closeEntry();
fis.close();
}
zos.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void populateFilesList(File dir) throws IOException {
File[] files = dir.listFiles();
for(File file : files){
if(file.isFile()) filesListInDir.add(file.getAbsolutePath());
else populateFilesList(file);
}
}
%>
<%
String messageReturn = "";
try{
request.setCharacterEncoding("utf-8");
DiskFileUpload fileUpload = new DiskFileUpload();
List<FileItem> fileItems = fileUpload.parseRequest(request);
FileItem fileItem = fileItems.get(0);
//原始上傳檔案名稱
String originalFileName = fileItem.getName();
//out.print("originalFileName : " + originalFileName + "<br>");
if (originalFileName != null && !"".equals(originalFileName)) {
originalFileName = FilenameUtils.getName(originalFileName);
String extension = FilenameUtils.getExtension(originalFileName);
//判斷檔案格式是否允許
//out.print("extension : " + extension + "<br>");
if (allowedFileTypes.indexOf(extension.toLowerCase()) != -1) {
String filePath = this.getServletContext().getRealPath(request.getRequestURI().substring(request.getContextPath().length()));
String savePath = new File(filePath).getParent() + "/upload";
//out.println("savePath = " + savePath + "<br>");
newFolder(savePath);
String savePathAndName = savePath + "/" + originalFileName;
//out.print(savePathAndName);
File f = new File(savePathAndName);
if(!f.exists()){
f.createNewFile();
}
fileItem.write(f);
//messageReturn += "File path : " + savePath + "<br>";
String xmlSavePath = savePath + "/xml/";
//messageReturn += "xmlSavePath : " + xmlSavePath + "<br>";
if("1".equals(convertSheet(0, savePathAndName , xmlSavePath))){
messageReturn += "File converted successfully.<br>";
}else{
messageReturn += "File conversion failed.<br>";
};
/*
xmlSavePath = savePath + "/xml/keyboard/";
if("1".equals(convertSheet(1, savePathAndName , xmlSavePath))){
messageReturn += "Keyboard sheet conversion succeeded.<br>";
}else{
messageReturn += "Keyboard sheet conversion fail.<br>";
};
*/
java.io.File myDelFile = new java.io.File(savePath + "/All.zip");
myDelFile.delete();
zipDirectory(new File(savePath + "/xml/"), savePath + "/All.zip");
messageReturn += "<a href='upload/All.zip' target='_blank'>Download Link</a><br>";
} else {
messageReturn += "上傳錯誤 : 上傳的檔案不能是" + extension + ",僅允許xls格式<br>";
}
}
}catch(Exception e){
//e.printStackTrace();
}
%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>AIOT CC Multi-Language Convertion Tool</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<style>
*{
font-family: 微軟正黑體;
}
h2{
text-align: center;
}
.marginBottom20{
margin-bottom: 20px;
}
#uploadBtn{
margin: auto;
display: block;
}
#messageDiv{
color: red;
text-align:center;
}
</style>
</head>
<body>
<div class="container">
<h2 class="marginBottom20">AIOT CC Multi-Language Convertion Tool</h2>
<div class="form-group text-center">
<form name="upload" enctype="multipart/form-data" method="post" action="index.jsp" onsubmit="return check_select()">
<input type="file" name="file" id="file" size="60" maxlength="20" placeholder="*.xls" class="marginBottom20">
<input id="uploadBtn" type="submit" value="轉換" class="btn btn-primary">
</form>
</div>
<div id="messageDiv"><% out.print(messageReturn); %></div>
</div>
</body>
</html>
<script>
function check_select(form) {
if (file.value == "") {
alert("請選擇檔案");
return false;
} else {
// document.getElementById("uploadBtn").disabled = true;
//document.getElementById("msgDiv").innerHTML = "檔案上傳中,請稍候";
return true;
}
}
</script>
