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

也許在許多專案上我們所說的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,869 Responses

  1. I would much rather prepare throughout the year than wait until the last minute when I can’t do much.

  2. Mathewenups表示:

    ordering drugs from canada: Express Canada Pharm – reputable canadian pharmacy

  3. Mathewenups表示:

    canada pharmacy reviews: pharmacies in canada that ship to the us – canada pharmacy online

  4. Heike表示:

    See What Large Pallets Tricks The Celebs Are Using large pallets (Heike)

  5. It is also a really controversial ingredient in the cosmetic industry as a result of not only is it a pores and skin irritant, but many formulation containing TEA are discovered to be contaminated with nitrosamines, which are linked to cancer.

  6. Darryldow表示:

    https://expresscanadapharm.com/# Express Canada Pharm

  7. Тут можно преобрести продвижение сайтов медицинских услуг seo под ключ

  8. DonaldAlurf表示:

    Express Canada Pharm: Express Canada Pharm – Express Canada Pharm

  9. Fantastic forum posts. Regards.
    legjobb online casino velemenyek https://combatcasino.info/credit-card-casino/ free online casino with no deposit bonus

  10. Many thanks! An abundance of data!
    fastest online casino payout https://uscasinoguides.com/casino-apps/ booming games online casino

  11. RobertSmelm表示:

    Express Canada Pharm: canadian pharmacy – canada drugstore pharmacy rx

  12. Darryldow表示:

    http://expresscanadapharm.com/# canadianpharmacyworld com

  13. Jamesemulk表示:

    UTLH: новое слово в сфере децентрализованных финансов и взаимопомощиСовременные финансовые инструменты зачастую оказываются неповоротливыми и неэффективными, особенно в условиях растущей инфляции и стремительно меняющегося рынка. Именно поэтому всё более актуальными становятся решения, способные объединить в себе высокую доходность, свободу от избыточной бюрократии и реальные преимущества для пользователей. UTLH идёт дальше привычных рамок «очередного токена», предлагая целостную экосистему, в которой каждый держатель получает доступ к уникальным финансовым возможностям и сильному сообществу. 1. UTLH — токен с реальной поддержкой сообщества1.1. Основа Универсальной Финансовой Помощи (УФП)В отличие от множества криптоактивов, которые существуют лишь в спекулятивных целях, UTLH — это:Залоговый инструмент, необходимый для участия в программе УФП.Фундамент взаимопомощи: совокупность держателей токена обеспечивает ликвидность и даёт возможность получения финансирования по льготным ставкам.1.2. Растущее сообщество UTL ClubМеждународная база участников и резидентов, для которых UTLH — не просто актив, а средство повышения уровня жизни.Нетворкинг и взаимовыручка: внутри клуба рождаются и развиваются проекты, основанные на доверии и поддержке. 2. Преимущества токена: ограниченная эмиссия и дефляционный механизм2.1. Чётко зафиксированное количествоВсего 957 315 токенов UTLH делает этот актив по-настоящему дефицитным. Это кардинально отличает его от токенов с неограниченной эмиссией, которые могут обесцениться при первом удобном случае «допечатки».2.2. Сжигание токеновДополнительный механизм burn со временем уменьшает и без того скромный объём UTLH в обращении. Для держателей это означает рост ценности каждого токена, ведь предложение сокращается, а спрос — растёт. 3. Стейкинг: стабильная доходность без лишних рисков3.1. 24% годовых (2% ежемесячно)Простая формула: зафиксировав определённое количество UTLH в стейкинге, держатель ежемесячно получает гарантированный процент.Минимальный вход: начать можно буквально с 1 токена, что делает продукт доступным даже для новичков в криптосфере.3.2. Безопасность и прозрачностьСмарт-контракт в сети Binance Smart Chain (BSC) контролирует распределение дохода.Открытый код: механика стейкинга прозрачна и проверяема любыми аудиторами или сообществом. 4. Универсальная Финансовая Помощь: новый взгляд на кредитование4.1. Залог в UTLH без бюрократииЧтобы воспользоваться программой УФП, участнику достаточно предоставить UTLH в залог. Это позволяет:Избавиться от необходимости оформлять классическую ипотеку или кредит в банке.Получить средства оперативно и без проволочек.4.2. Выгодные условия финансированияНизкие ставки по сравнению с банковскими продуктами.Гибкие сроки и форматы выплат, определяемые внутри сообщества и прописанные в смарт-контрактах. 5. Технологическая основа: Binance Smart Chain (BSC)5.1. Низкие комиссии и быстрая обработка транзакцийВ отличие от сетей с высокими «газовыми» затратами, BSC даёт возможность практически мгновенно переводить и стейкать UTLH без серьёзных расходов на комиссии.5.2. Широкая поддержка экосистемыUTLH легко интегрируется с DEX (например, PancakeSwap) и сторонними DeFi-протоколами. Это создаёт дополнительные сценарии использования токена и ещё больше укрепляет его ликвидность. 6. Риски и стратегия успеха6.1. Волатильность рынкаКак и любой актив на крипторынке, UTLH может подвержен ценовым колебаниям. Однако:УФП поддерживает постоянный спрос.Стейкинг стимулирует держателей не продавать токены, а держать их для регулярного дохода.6.2. Долгосрочная перспективаОграниченная эмиссия гарантирует, что с ростом сообщества и числа участников УФП предложение будет оставаться низким, а цена — подниматься.Механика сжигания обеспечивает дополнительный дефляционный эффект, что позволяет рассматривать UTLH как актив для долгосрочного хранения. 7. Как присоединиться и начать получать выгодыСоздайте BSC-кошелёк (MetaMask или Trust Wallet).Приобретите UTLH на доступной бирже или через личный кабинет клуба.Участвуйте в стейкинге: фиксированная годовая доходность в 24% не требует особых знаний и усилий.Используйте УФП: если вам нужны средства на бизнес, ипотеку или личные нужды, залог в виде UTLH позволит получить льготное финансирование без долгих проверок. ЗаключениеUTLH — это больше, чем просто криптовалюта. Он предлагает новую модель финансового взаимодействия, основанную на взаимной поддержке, прозрачности смарт-контрактов и дефицитной эмиссии. Благодаря стейкингу с высокой доходностью, программе льготного финансирования и сильной технологической базе (Binance Smart Chain), UTLH зарекомендовал себя как один из самых многообещающих проектов на рынке децентрализованных финансов.Если вы ищете стабильный инструмент для сохранения и приумножения капитала, хотите воспользоваться кредитом на выгодных условиях и цените сообщество, где интересы участников превыше всего, — UTLH может стать вашим надежным партнёром в мире криптовалютных инноваций.

  14. RobertSmelm表示:

    safe canadian pharmacy: legal canadian pharmacy online – Express Canada Pharm

  15. Spencergaply表示:

    Express Canada Pharm Express Canada Pharm canadapharmacyonline

  16. Darryldow表示:

    https://expresscanadapharm.shop/# Express Canada Pharm

  17. mostbet_hyKt表示:

    скачать mostbet на телефон https://mostbet20.com.kg/ .

  18. You’ll be able to accomplish this by forcing more air into the combustion chamber.

  19. DonaldAlurf表示:

    legal to buy prescription drugs from canada: Express Canada Pharm – Express Canada Pharm

  20. DonaldAlurf表示:

    legitimate canadian mail order pharmacy: canadian pharmacy 24h com safe – Express Canada Pharm

  21. Mathewenups表示:

    canada drugstore pharmacy rx: Express Canada Pharm – canada discount pharmacy

  22. This will not solely assist you to preserve your management over your company but in addition meet the investor’s claimed wants.

  23. You reported that effectively!
    alt live casino online https://casinocashstars.com/nfl-betting/ 5 reel drive online casinos

  24. Bunk Beds With Desk For Adults: The Good, The Bad, And The
    Ugly best Bunk beds For adults uk

發佈回覆給「Spencergaply」的留言 取消回覆

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