大專案中網頁多語系的維護方式

也許在許多專案上我們所說的Multi Language僅有繁體中文、簡體中文和英文,但在比較大型且跨國的專案上,可能會涉及更多的語言,例如:日文、俄文、德文、法文、西班牙文…等等,通常這些軟體內容的翻譯,需要經過更專業的單位來進行,也許這個單位不僅僅需要具備有這些國家的語言能力,也要具備有相當的軟體知識,才能配合當地民情翻譯出正確的文字,這部分往往需要專業的翻譯單位來進行。

而專業的翻譯單位通常並不具有軟體的製作能力,所以在軟體上要如何快速地進行協同作業,就變成一項非常重要的工作,而在許多專案上,我們會讓翻譯單位透過Excel來提供各國語系的翻譯文字,我們則透過軟體進行轉換,將其轉換至軟體能快速讀取的格式,講白一點就是將Excel檔案轉換成XML格式,並提供其他軟體進行讀取。

整體流程會如上圖所示,在拿到一個翻譯社提供所有語系的Excel資料後,我們會進行轉換,將文字轉換成多個XML檔案,並打包成一個ZIP檔讓轉換者進行下載。

今天我們主要來分享上圖藍色部分的處理程序,也就是那一隻轉換程式的結構和做法,我們將轉換程式設計成網頁版本,藉此提升易用性,而轉換程式操作Flow大致如下:

  1. 使用者上傳檔案(限制僅能上傳Excel檔案)
  2. 給出ZIP下載連結(提供使用者下載所有語系的XML檔)

操作上非常簡單,僅有上述這兩個步驟,而程式設計上採用JSP架構其運作邏輯如下:

  1. 檢查上傳檔案的格式、容量及相關資訊
  2. 擷取Excel中第一張工作表(也可以依照工作表名稱擷取)
  3. 將Excel中第一列視為語系標題,並當作存檔名稱(例如:English.xml)
  4. 將剩下來的每一列轉換為該檔案的語系資料,並建立XML檔案
  5. 將所有建立好的XML檔案進行打包(ZIP)
  6. 更新頁面產生ZIP檔下載路徑

在該轉換程式中,另外有利用到下述的JAVA Library:

  1. Apache POI – 處理與解析Excel檔案
  2. DOM4J – 建立XML檔案
  3. 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>

You may also like...

104,752 Responses

  1. Профессиональный сервисный центр по ремонту компьютероной техники в Москве.
    Мы предлагаем: качественный ремонт компьютеров
    Наши мастера оперативно устранят неисправности вашего устройства в сервисе или с выездом на дом!

  2. yyfgashyo表示:

    Advertising allows us to keep providing you awesome games for free. Advertising allows us to keep providing you awesome games for free. Welcome to Dress Up Games! This website was founded in 1998 and has been updated regularly ever since. We have a very long experience with finding and choosing the very best dress up and makeover games the web has to offer. Create the coolest looks ever with the Superstar Makeovers game! Your model is none other than Barbie! You need to help her get ready for the following photo shoots and choose the style that fits her best. Experiment with all the colors you can come up with. We started developing and producing our own games in 2006, starting slowly but now we usually release a few games each month. Using our expertise, long experience, and keeping in touch with our users we take pride in knowing exactly what our visitors are looking for and giving them exactly that.
    https://www.pcgamesinsider.biz/company/16150/gameway/
    Game 7 (if necessary): Lakers at Grizzlies; Sunday, April 30; Time and TV TBD When do the NBA playoffs start?:Play-in tournament, NBA postseason dates, what to know You can watch the 2023-24 NBA regular season, Play-In Tournament, Playoff, and NBA finals live exclusively on NBA League Pass, Bally Sports, local NBC sports, ROOT Sports, Altitude, ABC, ESPN, NBA TV, and TNT. The Ringer’s Kevin O’Connor tweeted out all of the scenarios provided by the NBA: There’s only one national game on the Monday slate as the Sacramento Kings will host the Memphis Grizzlies with the broadcast on NBA TV. That’s the only matchup between two true playoff contenders right now, but that doesn’t mean there aren’t any games that League Pass subscribers shouldn’t be hoping to tune in for. RadioTimes brings you all the details on how to watch the NBA Finals in the UK, including full TV schedule and coverage details.

  3. Используйте промокод в cryptoboss casino и получите уникальные бонусы|Специальное предложение от cryptoboss casino
    Бонусный код для cryptoboss casino|Используйте промокод в cryptoboss casino и увеличьте свои шансы на победу
    Только сегодня! cryptoboss casino промокод|Секретный код для cryptoboss casino
    cryptoboss casino промокод – ваш ключ к выигрышу|cryptoboss casino – играйте со скидкой по промокоду
    криптобосс промокод на бонус при регистрации cryptoboss casino промокод .

    Успех в cryptoboss casino с промокодом
    Получите бонусный приз с cryptoboss casino промокодом
    Увеличьте свои шансы на победу с cryptoboss casino промокодами|Получите уникальное предложение с cryptoboss casino промокодами
    Успейте воспользоваться cryptoboss casino промокодом и выиграть больше|cryptoboss casino – ваши шансы на победу с промокодами|Бонусная программа cryptoboss casino с уникальным промокодом|Получите привилегии в cryptoboss casino с промокодом|Получите уникальные бонусы в cryptoboss casino по промокоду|Успейте активировать промокод для увеличения выигрыша|Получите дополнительные бонусы с cryptoboss casino промокодом|cryptoboss casino промокод – ваш шанс на успех|cryptoboss casino – ваша удача по промокоду|cryptoboss casino промокоды – ваш путь к успеху|Получите уникальные бонусы с cryptoboss casino промокодом|cryptoboss casino – ваш шанс на успех с промокодом|Уникальное предложение от cryptoboss casino с промокодом|Играйте в cryptoboss casino с уникальными привилегиями по промокоду|Используйте cryptoboss casino промокод для увеличения шансов на победу|Уникальные предложения от cryptoboss casino с промокодом|Получите привилегии с cryptoboss casino промокодом|Получите дополнительные бонусы в crypt

  4. Jamesfeame表示:

    betine betine promosyon kodu 2024 betine

  5. ngentot anak表示:

    Way cool! Some very valid points! I appreciate you penning this post and also the rest of the website is also really good.

  6. Elmo表示:

    You’ll Never Guess This Bunk Bed Kids’s Benefits bunk [Elmo]

  7. Jens表示:

    Coffee Grinders Types Tools To Ease Your Daily Lifethe
    One Coffee Grinders Types Trick That Should Be Used By Everyone
    Be Able To coffee grinders types – Jens,

  8. Thank you for sharing this very good write-up. Very inspiring! (as always, btw)

  9. Krista表示:

    Five Things Everybody Gets Wrong About Foldable Electric Scooters
    high-capacity mobility scooters (Krista)

  10. Shelia表示:

    5 Killer Quora Answers On U Shaped Sectional With Chaise
    u shaped sectional with chaise (Shelia)

  11. ремонт кондиционеров表示:

    <a href=”https://remont-kondicionerov-wik.ru”>профессиональный ремонт кондиционеров</a>

  12. Great site! I recommend it to everyone!tarjima kinolar

  13. Hi there! I just want to give a huge thumbs up for the nice information you’ve here on this post. I will probably be coming again to your weblog for extra soon.

  14. Teresita表示:

    The Best Advice You Could Receive About Treehouse Bunk Bed bunk bed with
    tree house (Teresita)

  15. bokep sma表示:

    I blog often and I genuinely appreciate your information. The article has truly peaked my interest. I am going to book mark your site and keep checking for new information about once a week. I subscribed to your RSS feed too.

  16. GeorgeAmarp表示:

    http://starzbet.shop/# starzbet guncel giris

  17. bos88表示:

    chord before you go chord before you go chord before you go
    What a information of un-ambiguity and preserveness of
    precious know-how on the topic of unexpected feelings.

  18. Also, whatever happened to those vaunted shields that in the television show always protected the ship from harm? In this movie the shields are about as effective as paper-mache as the Enterprise is strafed, bombed, rocketed, smashed, tossed, toppled, and shaken like a baby’s toy.

  19. Новые зеркала Cryptoboss Casino уже здесь!, играйте без проблем!
    Новое зеркало Cryptoboss Casino доступно для всех!, бесперебойный доступ гарантированы.
    Самое популярное зеркало Cryptoboss Casino ждет вас прямо сейчас, забудьте об другие варианты!
    Узнавайте самую актуальную информацию на зеркале Cryptoboss Casino!, не пропустите свой шанс!
    Без зеркала Cryptoboss Casino никуда!, играйте без риска без лишних хлопот!
    криптобосс зеркало рабочее криптобосс официальное зеркало .

  20. The 10 Most Scariest Things About Curved Leather Sofa curved leather sofa

  21. ngentot pepek表示:

    Good article! We will be linking to this particularly great post on our site. Keep up the good writing.

  22. nhkの料理表示:

    These include equity and debt underwriting, corporate lending and project financing, merger and acquisitions advisory services, securitization, treasury management, market risk management, debt and equity research and institutional sales and trading.

  23. 5 Killer Quora Answers On Strollers 3 Wheels Strollers 3 wheels

  24. Catalina表示:

    What Can A Weekly Retro Fridge Freezer Project Can Change Your Life side by side fridge freezer for entertainment spaces (Catalina)

  25. Valeria表示:

    How The 10 Worst Retro White Fridge Freezer Errors Of All Time Could Have Been Prevented side by side fridge
    freezer with humidity-controlled drawers (Valeria)

  26. Bebe表示:

    Where Will Replacement Audi Key Be 1 Year From Right Now?
    audi key programmer [Bebe]

  27. Мой телефон перестал заряжаться, и я не знал, что делать. По совету друга обратился в этот сервисный центр. Мастера быстро нашли проблему и устранили её. Теперь мой телефон снова в строю! Рекомендую всем: где починить телефон.

  28. Why Private Psychiatrist Might Be Your Next Big Obsession private psychotherapist (gogs.dodges.It)

  29. See What Kids Bunk Beds For Sale Tricks The Celebs
    Are Utilizing Kids Bunk Beds For Sale, https://Qooh.Me/Dishmodem77,

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。