14 March 2015

Text localization in Unity 4.6 and Unity 5

Unity's UI system used to be very limited, so most people turned to the asset store for UI plugins. One of the most popular ones is NGUI. I've been using NGUI for more than two years now. Needless to say that I was happy to hear that the developer of NGUI was going to work for Unity on a new UI system. Apparently he did that for only a year but the result I hoped for has been achieved.

Unity 4.6 and 5 have a new UI system and it is very resemblant to NGUI. I could convert my NGUI version to the unity version with minimal changes in the code. But with all new components of course.

But there was one capital feature missing: NGUI has an easy text localization system and Unity hasn't build an alternative for that. So I rewrote the localization code from NGUI so it would work with the new unity UI. I also changed a few things so it would fit my needs better, it is not a one to one conversion. I have asked permission to post it on this blog.

NGUI expects a Localization.csv file somewhere in a Resources folder. In my version you'll see there's a Resources folder containing a single asset. In the files collection of that asset you can assign any csv anywhere in your project. That way you can have multiple csv files containing localization.

The reason to do this was that the excel sheet containing the localization became very large and hard to maintain, so I wanted the ability to split it up into multiple sheets, resulting in multiple csv's.

To be able to export these sheets easily in multiple csv's I found this page which I used to write the program below. It takes an xlsx filename and an output path as input and will generate a csv for each sheet in the excel file. In my unity project I have this excel file and my build process uses this program to generate these csv's that are linked in the localization asset. That way I always have the latest version of the localization in the excel file build into the game.

[Edit]For the program below to work you need to have Excel installed and the correct oledb drivers. If you have a 64bit version of excel make sure to compile the program as 64bit or 32bit if Excel is 32bit.[/Edit]

As always this software is free to use at will, enjoy!

using System;
using System.Data;
using System.Data.OleDb;
using System.IO;

namespace Xls2CsvConverter
{
 class Program
 {
  static void Main(string[] args)
  {
   Console.Out.WriteLine("Source file: " + args[0]);
   Console.Out.WriteLine("Target path: " + args[1]);

   string excelFile = Path.GetFullPath(args[0]);
   string csvPath = Path.GetFullPath(args[1]);


   ConvertExcelToCsv(excelFile, csvPath);

   Console.Out.WriteLine("Done");
  }

  static void ConvertExcelToCsv(string excelFilePath, string csvOutputFile)
  {
   if (!File.Exists(excelFilePath)) throw new FileNotFoundException(excelFilePath);
   //if (File.Exists(csvOutputFile)) throw new ArgumentException("File exists: " + csvOutputFile);

   // connection string
   var connectionString =
    String.Format("Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties='Excel 8.0;HDR=No'",
     excelFilePath);
   var oleDbConnection = new OleDbConnection(connectionString);

   // get schema, then data
   try
   {
    oleDbConnection.Open();
    var schemaTable = oleDbConnection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
    for (int i = 0; i < schemaTable.Rows.Count; ++i)
    {
     string worksheet = schemaTable.Rows[i]["table_name"].ToString().Replace("'", "");
     string sql = String.Format("select * from [{0}]", worksheet);
     var dataTable = new DataTable();
     var dataAdapter = new OleDbDataAdapter(sql, oleDbConnection);
     dataAdapter.Fill(dataTable);

     string csvFile = Path.Combine(csvOutputFile, worksheet.Replace("$", "") + ".csv");
     MakeWritable(csvFile);
     using (var wtr = new StreamWriter(csvFile))
     {
      foreach (DataRow row in dataTable.Rows)
      {
       bool firstLine = true;
       foreach (DataColumn col in dataTable.Columns)
       {
        if (!firstLine)
        {
         wtr.Write(",");
        }
        else
        {
         firstLine = false;
        }
        var data = row[col.ColumnName].ToString().Replace("\"", "\"\"");
        wtr.Write("\"{0}\"", data);
       }
       wtr.WriteLine();
      }
     }
    }
   }
   finally
   {
    // free resources
    oleDbConnection.Close();
   }
   
  }

  private static void MakeWritable(string path)
  {
   if (!File.Exists(path)) return;
   FileAttributes attributes = File.GetAttributes(path);

   if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
   {
    // Make the file RW
    attributes = RemoveAttribute(attributes, FileAttributes.ReadOnly);
    File.SetAttributes(path, attributes);
   } 
  }

  private static FileAttributes RemoveAttribute(FileAttributes attributes, FileAttributes attributesToRemove)
  {
   return attributes & ~attributesToRemove;
  }
 }
}

Edit: As per request I added a demo project here. You'll see I have a GameController class who calls Init on the text localization class and selects the correct language. In the localization asset in the Resources folder I added a demonstration CSV file.