大專案中網頁多語系的維護方式
也許在許多專案上我們所說的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>
Permainan slot bisa dimainkan dengan berbagai taruhan https://garuda888.top/# Slot menawarkan kesenangan yang mudah diakses
http://bonaslot.site/# Kasino sering memberikan hadiah untuk pemain setia
preman69 preman69 Slot dengan tema film terkenal menarik banyak perhatian
В Vovan Casino вы откроете для себя уникальный игровой опыт. В нашем казино вы найдете разнообразие игр, включая карточные игры, слоты и рулетку. Для наших гостей доступны специальные предложения, которые помогают усилить азарт. Каждый день в Vovan Casino — это возможность испытать удачу. Участвуйте в эксклюзивных событиях Vovan Casino, чтобы получить незабываемые эмоции. Это еще и возможность сэкономить время, а также получить максимум от игрового процесса. Каждая игра дарит вам не только удовольствие, но и шанс стать победителем — https://vovan-casinowave.icu/. Когда лучше всего начинать играть? Ответ прост: в любое время! Вот несколько советов, которые помогут вам сделать игру комфортнее: Рекомендуем ознакомиться с нашими правилами, чтобы сделать игровой процесс приятным и безопасным. Опытным участникам доступны особые условия, которые позволят вам добиться лучшего результата. Если вы давно не играли, начните с бесплатных демо-версий игр.
Feel free to visit my web page … best Online Poker sites
http://slotdemo.auction/# Banyak kasino memiliki program loyalitas untuk pemain
Introduction To The Intermediate Guide On Good
Prams Top Prams
Slot menawarkan kesenangan yang mudah diakses https://garuda888.top/# Pemain sering berbagi tips untuk menang
Добро пожаловать в R7 Казино, идеальное место для азартных игроков, ищущих шанс стать победителем. В R7 Казино вас ждет большой выбор увлекательных игр, от популярных слотов до эксклюзивных столов с живыми дилерами. https://10kilogramm.ru/ В R7 Казино каждая игра — это реальная возможность для выигрыша и веселья.
Почему стоит выбрать именно нас? В R7 Казино важен каждый игрок, и мы предлагаем высокий уровень комфорта и защиты ваших данных. Кроме того, мы часто проводим акции и турниры с крупными призами, что позволяет игрокам увеличить свои шансы на успех.
В каком случае стоит начать играть в R7 Казино? Не откладывайте, начните играть в R7 Казино уже сегодня и испытайте удачу на своем пути к победе. Вот несколько причин, почему вам стоит выбрать именно нас:
Читайте наши условия игры, чтобы избежать неприятных ситуаций и играть с уверенностью.
Наши постоянные игроки могут получить специальные бонусы и привилегии, доступные только им.
Мы предлагаем демо-режимы для новых игроков, чтобы вы могли освоить игру без стресса и потерь.
R7 Казино — это шанс для каждого на крупные выигрыши и незабываемые эмоции!
http://slotdemo.auction/# Kasino menyediakan layanan pelanggan yang baik
http://slot88.company/# Banyak pemain menikmati bermain slot secara online
I have been absent for a while, but now I remember why I used to love this web site. Thanks , I will try and check back more frequently. How frequently you update your web site?
Slot memberikan kesempatan untuk menang besar: slot demo gratis – demo slot pg
Banyak kasino menawarkan permainan langsung yang seru http://preman69.tech/# п»їKasino di Indonesia sangat populer di kalangan wisatawan
The Little-Known Benefits To Replacement Upvc Window Handles upvc Window Repairs near me
Slot dengan pembayaran tinggi selalu diminati: bonaslot – bonaslot
http://slot88.company/# Kasino menawarkan pengalaman bermain yang seru
Permainan slot bisa dimainkan dengan berbagai taruhan: slot88.company – slot 88
preman69.tech preman69.tech Kasino mendukung permainan bertanggung jawab
A Comprehensive Guide To Second Hand Double Buggy From Start
To Finish Ready To Grow Double Stroller (http://Www.Metooo.Com)
The Reason Why Mental Health Psychiatrist Is Much More Hazardous Than You Think full mental health assessment, Edna,
5 Killer Quora Answers On Psychiatric Assessment UK
psychiatric assessment uk (Alta)
https://slot88.company/# Slot memberikan kesempatan untuk menang besar
You’ll Never Guess This Double Glazing Doors Near Me’s Benefits fit
http://slot88.company/# Slot dengan tema budaya lokal menarik perhatian
This Is The Complete Listing Of Pram 2in1 Dos And Don’ts car seat 2 In 1 stroller
Slot dengan grafis 3D sangat mengesankan https://bonaslot.site/# Keseruan bermain slot selalu menggoda para pemain
10 Things That Your Family Taught You About Online Psychiatric Assessment UK online psychiatric Assessment
Mesin slot menawarkan berbagai tema menarik: slot88 – slot 88
Pemain bisa menikmati slot dari kenyamanan rumah: slot 88 – slot88.company