[glom] Move po file (gettext) import and export into libglom and test it.



commit ffd6bd9a6e2dfbb9fbbd0b464b812352eb9a7595
Author: Murray Cumming <murrayc murrayc com>
Date:   Sat Jan 7 23:05:00 2012 +0100

    Move po file (gettext) import and export into libglom and test it.
    
    * glom/mode_design/translation/window_translations.[h|cc]:
    load_from_document(), save_to_document(): Move the collecting of
    translatable items to:
    * glom/libglom/document/document.[h|cc]: get_translatable_items();
    Also move the gettext/po import/export to:
    * Makefile_libglom.am:
    * glom/libglom/filelist.am:
    * glom/libglom/translations_po.[h|cc]: write_translations_to_po_file()
    and import_translations_from_po_file().
    
    * Makefile_tests.am
    * tests/translations_po/data/test.po:
    * tests/translations_po/test_document_export_po.cc:
    * tests/translations_po/test_document_import_po.cc: Add tests of the new
    libglom functions.
    
    * Makefile_glom.am
    * glom/glom_export_po.cc: Added a new command-line tool that uses the
    new libglom API.

 ChangeLog                                          |   24 +
 Makefile_glom.am                                   |    1 -
 Makefile_libglom.am                                |   10 +-
 Makefile_tests.am                                  |   25 +-
 glom/glom_export_po.cc                             |  183 +++
 glom/libglom/document/document.cc                  |   63 ++
 glom/libglom/document/document.h                   |    6 +-
 glom/libglom/filelist.am                           |    5 +-
 glom/libglom/translations_po.cc                    |  263 +++++
 glom/libglom/translations_po.h                     |   35 +
 .../mode_design/translation/window_translations.cc |  381 +------
 glom/mode_design/translation/window_translations.h |    4 +-
 po/POTFILES.in                                     |    2 +
 tests/test_document_load_translations.cc           |   78 ++
 tests/translations_po/data/test.po                 | 1163 ++++++++++++++++++++
 tests/translations_po/test_document_export_po.cc   |  120 ++
 tests/translations_po/test_document_import_po.cc   |  128 +++
 17 files changed, 2141 insertions(+), 350 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index d0276ba..946d1d6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,29 @@
 2012-01-07  Murray Cumming  <murrayc murrayc com>
 
+	Move po file (gettext) import and export into libglom and test it.
+
+	* glom/mode_design/translation/window_translations.[h|cc]:
+	load_from_document(), save_to_document(): Move the collecting of
+	translatable items to:
+	* glom/libglom/document/document.[h|cc]: get_translatable_items();
+	Also move the gettext/po import/export to:
+	* Makefile_libglom.am:
+	* glom/libglom/filelist.am:
+	* glom/libglom/translations_po.[h|cc]: write_translations_to_po_file()
+	and import_translations_from_po_file().
+
+	* Makefile_tests.am
+	* tests/translations_po/data/test.po:
+	* tests/translations_po/test_document_export_po.cc:
+	* tests/translations_po/test_document_import_po.cc: Add tests of the new 
+	libglom functions.
+
+	* Makefile_glom.am
+	* glom/glom_export_po.cc: Added a new command-line tool that uses the
+	new libglom API.
+
+2012-01-07  Murray Cumming  <murrayc murrayc com>
+
 	test_document_load: Fix a typo.
 
 	* tests/test_document_load.cc: Check the correct variable.
diff --git a/Makefile_glom.am b/Makefile_glom.am
index e59d9f6..0c3adb3 100644
--- a/Makefile_glom.am
+++ b/Makefile_glom.am
@@ -399,7 +399,6 @@ glom_glom_SOURCES =							\
 glom_all_libs = $(win_resfile) \
 	glom/libglom/libglom-$(GLOM_ABI_VERSION).la $(libglom_all_libs) \
 	$(GLOM_LIBS) $(INTLLIBS)
-glom_all_libs += -lgettextpo
 
 glom_glom_LDADD = $(glom_all_libs)
 
diff --git a/Makefile_libglom.am b/Makefile_libglom.am
index f0a39c7..71d7510 100644
--- a/Makefile_libglom.am
+++ b/Makefile_libglom.am
@@ -47,7 +47,7 @@ libglom_d_b_view_include_HEADERS = $(libglom_d_b_view_headers)
 
 glom_libglom_libglom_ GLOM_ABI_VERSION@_la_SOURCES = $(libglom_sources)
 
-libglom_all_libs = $(LIBGLOM_LIBS) $(PYTHON_LIBS) $(BOOST_PYTHON_LIBS) $(GCOV_CFLAGS) 
+libglom_all_libs = $(LIBGLOM_LIBS) $(PYTHON_LIBS) $(BOOST_PYTHON_LIBS) -lgettextpo $(GCOV_CFLAGS)
 glom_libglom_libglom_ GLOM_ABI_VERSION@_la_LIBADD = $(libglom_all_libs)
 
 if HOST_WIN32
@@ -85,7 +85,9 @@ pkgconfig_DATA = glom/libglom/glom- GLOM_ABI_VERSION@.pc
 glom_commandline_ldadd = glom/libglom/libglom-$(GLOM_ABI_VERSION).la $(libglom_all_libs)
 glom_commandline_cppflags = $(glom_includes) $(LIBGLOM_CFLAGS) $(PYTHON_CPPFLAGS) $(BOOST_PYTHON_CFLAGS) $(glom_defines)
 
-bin_PROGRAMS = glom/glom_create_from_example glom/glom_test_connection
+bin_PROGRAMS = glom/glom_create_from_example \
+  glom/glom_test_connection \
+  glom/glom_export_po
 
 glom_glom_create_from_example_SOURCES = glom/glom_create_from_example.cc
 glom_glom_create_from_example_LDADD = $(glom_commandline_ldadd)
@@ -94,3 +96,7 @@ glom_glom_create_from_example_CPPFLAGS = $(glom_commandline_cppflags)
 glom_glom_test_connection_SOURCES = glom/glom_test_connection.cc
 glom_glom_test_connection_LDADD = $(glom_commandline_ldadd)
 glom_glom_test_connection_CPPFLAGS = $(glom_commandline_cppflags)
+
+glom_glom_export_po_SOURCES = glom/glom_export_po.cc
+glom_glom_export_po_LDADD = $(glom_commandline_ldadd)
+glom_glom_export_po_CPPFLAGS = $(glom_commandline_cppflags)
diff --git a/Makefile_tests.am b/Makefile_tests.am
index afddd79..b106767 100644
--- a/Makefile_tests.am
+++ b/Makefile_tests.am
@@ -47,7 +47,9 @@ check_PROGRAMS =						\
 	tests/test_selfhosting_new_then_change_columns \
 	tests/test_selfhosting_sqlinjection \
 	tests/import/test_parsing \
-	tests/import/test_signals
+	tests/import/test_signals \
+	tests/translations_po/test_document_export_po \
+	tests/translations_po/test_document_import_po
 
 TESTS =	tests/test_document_load	\
 	tests/test_document_load_and_change	\
@@ -78,7 +80,10 @@ TESTS =	tests/test_document_load	\
 	tests/test_selfhosting_new_then_change_columns \
 	tests/test_selfhosting_sqlinjection \
 	tests/import/test_parsing \
-	tests/import/test_signals
+	tests/import/test_signals \
+	tests/translations_po/test_document_export_po \
+	tests/translations_po/test_document_import_po
+
 
 # We also set this in Makefile.am, with +=,
 # but this is the first use, where we must use =
@@ -155,12 +160,16 @@ tests_python_test_python_module_CPPFLAGS = $(tests_cppflags)
 # Distribute the tests data:
 dist_noinst_DATA = \
 	tests/import/data/albums.csv \
+	tests/translations_po/data/test.po \
 	tests/test_image.jpg
 
-# Let the .cc source code know about this path: TODO: Actually use this.
+# Let the .cc source code know about this path:
 glom_test_import_defines = -DGLOM_TESTS_IMPORT_DATA_NOTINSTALLED=\""$(abs_top_srcdir)/tests/import/data/"\"
 glom_test_image_defines = -DGLOM_TESTS_IMAGE_DATA_NOTINSTALLED=\""$(abs_top_srcdir)/tests/"\"
 
+# Let the .cc source code know about this path:
+glom_test_translations_po_defines = -DGLOM_TESTS_TRANSLATIONS_PO_DATA_NOTINSTALLED=\""$(abs_top_srcdir)/tests/translations_po/data/"\"
+
 
 tests_import_test_parsing_SOURCES =	\
 	glom/import_csv/csv_parser.cc	\
@@ -180,6 +189,16 @@ tests_import_test_signals_SOURCES =	\
 tests_import_test_signals_LDADD = $(tests_ldadd)
 tests_import_test_signals_CPPFLAGS = $(tests_cppflags)
 
+tests_translations_po_test_document_export_po_SOURCES =	\
+	tests/translations_po/test_document_export_po.cc
+tests_translations_po_test_document_export_po_LDADD = $(tests_ldadd)
+tests_translations_po_test_document_export_po_CPPFLAGS = $(tests_cppflags)
+
+tests_translations_po_test_document_import_po_SOURCES =	\
+	tests/translations_po/test_document_import_po.cc
+tests_translations_po_test_document_import_po_LDADD = $(tests_ldadd)
+tests_translations_po_test_document_import_po_CPPFLAGS = $(tests_cppflags) $(glom_test_translations_po_defines)
+
 # Note that wherever we use this we must also use glom_test_image_defines.
 sources_test_utils = tests/test_utils.h \
 	tests/test_utils.cc
diff --git a/glom/glom_export_po.cc b/glom/glom_export_po.cc
new file mode 100644
index 0000000..06443a3
--- /dev/null
+++ b/glom/glom_export_po.cc
@@ -0,0 +1,183 @@
+/* Glom
+ *
+ * Copyright (C) 2012 Openismus GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+71 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+// For instance:
+// glom_export_po /opt/gnome30/share/doc/glom/examples/example_music_collection.glom --output="/home/someone/something.po"
+
+#include "config.h"
+
+#include <libglom/init.h>
+#include <libglom/translations_po.h>
+#include <giomm/file.h>
+#include <glibmm/optioncontext.h>
+#include <glibmm/convert.h>
+#include <glibmm/miscutils.h>
+#include <iostream>
+
+#include <glibmm/i18n.h>
+
+class GlomCreateOptionGroup : public Glib::OptionGroup
+{
+public:
+  GlomCreateOptionGroup();
+
+  //These instances should live as long as the OptionGroup to which they are added,
+  //and as long as the OptionContext to which those OptionGroups are added.
+  std::string m_arg_filepath_output;
+  bool m_arg_version;
+};
+
+GlomCreateOptionGroup::GlomCreateOptionGroup()
+: Glib::OptionGroup("glom_export_po", _("Glom options"), _("Command-line options")),
+  m_arg_version(false)
+{
+  Glib::OptionEntry entry; 
+  entry.set_long_name("output-path");
+  entry.set_short_name('o');
+  entry.set_description(_("The path at which to save the created .po file, such as /home/someuser/somefile.po ."));
+  add_entry_filename(entry, m_arg_filepath_output);
+  
+  entry.set_long_name("version");
+  entry.set_short_name('V');
+  entry.set_description(_("The version of this application."));
+  add_entry(entry, m_arg_version);
+}
+
+int main(int argc, char* argv[])
+{
+  Glom::libglom_init();
+  
+  Glib::OptionContext context;
+  GlomCreateOptionGroup group;
+  context.set_main_group(group);
+  
+  try
+  {
+    context.parse(argc, argv);
+  }
+  catch(const Glib::OptionError& ex)
+  {
+      std::cout << _("Error while parsing command-line options: ") << std::endl << ex.what() << std::endl;
+      std::cout << _("Use --help to see a list of available command-line options.") << std::endl;
+      return 0;
+  }
+  catch(const Glib::Error& ex)
+  {
+    std::cout << "Error: " << ex.what() << std::endl;
+    return 0;
+  }
+
+  if(group.m_arg_version)
+  {
+    std::cout << PACKAGE_STRING << std::endl;
+    return 0;
+  }
+
+  // Get a URI for a glom file:
+  Glib::ustring input_uri;
+
+  // The GOption documentation says that options without names will be returned to the application as "rest arguments".
+  // I guess this means they will be left in the argv. Murray.
+  if(input_uri.empty() && (argc > 1))
+  {
+     const char* pch = argv[1];
+     if(pch)
+       input_uri = pch;
+  }
+
+  if(input_uri.empty())
+  {
+    std::cerr << "Please specify a glom file." << std::endl;
+    std::cerr << std::endl << context.get_help() << std::endl;
+    return EXIT_FAILURE;
+  }
+  
+  //Get a URI (file://something) from the filepath:
+  Glib::RefPtr<Gio::File> file_input = Gio::File::create_for_commandline_arg(input_uri);
+
+  //Make sure it is really a URI:
+  input_uri = file_input->get_uri();
+
+  if(!file_input->query_exists())
+  {
+    std::cerr << _("Glom: The file does not exist.") << std::endl;
+    std::cerr << "uri: " << input_uri << std::endl;
+
+    std::cerr << std::endl << context.get_help() << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  const Gio::FileType file_type = file_input->query_file_type();
+  if(file_type == Gio::FILE_TYPE_DIRECTORY)
+  {
+    std::cerr << _("Glom: The file path is a directory instead of a file.") << std::endl;
+    std::cerr << std::endl << context.get_help() << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  //Check the output path: 
+  if(group.m_arg_filepath_output.empty())
+  {
+    std::cerr << "Please specify an output path." << std::endl;
+    std::cerr << std::endl << context.get_help() << std::endl;
+    return EXIT_FAILURE;
+  }
+  
+  //Get a URI (file://something) from the filepath:
+  Glib::RefPtr<Gio::File> file_output = Gio::File::create_for_commandline_arg(group.m_arg_filepath_output);
+  const Glib::ustring ouput_uri = file_output->get_uri();
+
+  if(file_output->query_exists())
+  {
+    std::cerr << _("Glom: The output file aready exists.") << std::endl;
+    std::cerr << "uri: " << ouput_uri << std::endl;
+
+    std::cerr << std::endl << context.get_help() << std::endl;
+    return EXIT_FAILURE;
+  }
+
+
+  // Load the document:
+  Glom::Document document;
+  document.set_file_uri(input_uri);
+  int failure_code = 0;
+  const bool test = document.load(failure_code);
+  //std::cout << "Document load result=" << test << std::endl;
+
+  if(!test)
+  {
+    std::cerr << "Document::load() failed with failure_code=" << failure_code << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  const bool succeeded = 
+    Glom::write_translations_to_po_file(&document, ouput_uri, "de_DE");
+  if(!succeeded)
+  {
+    std::cerr << "Po file creation failed." << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  std::cout << "Po file created at: " << ouput_uri << std::endl;
+  
+  Glom::libglom_deinit();
+
+  return EXIT_SUCCESS;
+}
diff --git a/glom/libglom/document/document.cc b/glom/libglom/document/document.cc
index 4028529..7f95bfb 100644
--- a/glom/libglom/document/document.cc
+++ b/glom/libglom/document/document.cc
@@ -4405,6 +4405,69 @@ std::vector<Glib::ustring> Document::get_translation_available_locales() const
   return m_translation_available_locales;
 }
 
+Document::type_list_translatables Document::get_translatable_items()
+{
+  type_list_translatables result;
+
+  //Add tables:
+  type_listTableInfo tables = get_tables();
+  for(type_listTableInfo::const_iterator iter = tables.begin(); iter != tables.end(); ++iter)
+  {
+    sharedptr<TableInfo> tableinfo = *iter;
+    if(!tableinfo)
+      continue;
+
+    result.push_back(tableinfo);
+
+    const Glib::ustring table_name = tableinfo->get_name();
+
+    //The table's field titles:
+    type_vec_fields fields = get_table_fields(table_name);
+    for(type_vec_fields::iterator iter = fields.begin(); iter != fields.end(); ++iter)
+    {
+      sharedptr<Field> field = *iter;
+      if(!field)
+        continue;
+
+      result.push_back(field);
+
+      //Custom Choices, if any:
+      if(field->get_glom_type() == Field::TYPE_TEXT) //Choices for other field types could not be translated.
+      {
+        type_list_translatables list_choice_items;
+        Document::fill_translatable_custom_choices(field->m_default_formatting, list_choice_items);
+        result.insert(result.end(), list_choice_items.begin(), list_choice_items.end());
+      }
+    }
+
+    //The table's relationships:
+    type_vec_relationships relationships = get_relationships(table_name);
+    result.insert(result.end(), relationships.begin(), relationships.end());
+
+    //The table's report titles:
+    type_listReports listReports = get_report_names(table_name);
+    for(type_listReports::iterator iter = listReports.begin(); iter != listReports.end(); ++iter)
+    {
+      const Glib::ustring report_name = *iter;
+      sharedptr<Report> report = get_report(table_name, report_name);
+      if(!report)
+        continue;
+
+      result.push_back(report);
+      
+      //Translatable report items:
+      type_list_translatables list_layout_items = get_translatable_report_items(table_name, report_name);
+      result.insert(result.end(), list_layout_items.begin(), list_layout_items.end());
+    }
+
+    //The table's translatable layout items:
+    type_list_translatables list_layout_items = get_translatable_layout_items(table_name);
+    result.insert(result.end(), list_layout_items.begin(), list_layout_items.end());
+  } //for
+
+  return result;
+}
+
 Document::type_list_translatables Document::get_translatable_layout_items(const Glib::ustring& table_name)
 {
   type_list_translatables result;
diff --git a/glom/libglom/document/document.h b/glom/libglom/document/document.h
index 1107bbf..266b4db 100644
--- a/glom/libglom/document/document.h
+++ b/glom/libglom/document/document.h
@@ -258,8 +258,7 @@ public:
   type_list_layout_groups get_data_layout_groups_default(const Glib::ustring& layout_name, const Glib::ustring& parent_table_name, const Glib::ustring& layout_platform = Glib::ustring()) const;
 
   typedef std::list< sharedptr<TranslatableItem> > type_list_translatables;
-  type_list_translatables get_translatable_layout_items(const Glib::ustring& table_name);
-  type_list_translatables get_translatable_report_items(const Glib::ustring& table_name, const Glib::ustring& report_title);
+  type_list_translatables get_translatable_items();
 
   static void fill_translatable_custom_choices(FieldFormatting& formatting, type_list_translatables& the_list);
 
@@ -523,6 +522,9 @@ private:
 
   void fill_sort_field_details(const Glib::ustring& parent_table_name, FieldFormatting::type_list_sort_fields& sort_fields) const;
 
+  type_list_translatables get_translatable_layout_items(const Glib::ustring& table_name);
+  type_list_translatables get_translatable_report_items(const Glib::ustring& table_name, const Glib::ustring& report_title);
+
 
   /// If the attribute is not there, then the default will be returned.
   static bool get_node_attribute_value_as_bool(const xmlpp::Element* node, const Glib::ustring& strAttributeName, bool value_default = false);
diff --git a/glom/libglom/filelist.am b/glom/libglom/filelist.am
index 59e3dac..3ecc651 100644
--- a/glom/libglom/filelist.am
+++ b/glom/libglom/filelist.am
@@ -25,7 +25,8 @@ libglom_toplevel_headers =				\
 	glom/libglom/standard_table_prefs_fields.h	\
 	glom/libglom/utils.h				\
 	glom/libglom/db_utils.h \
-	glom/libglom/report_builder.h
+	glom/libglom/report_builder.h \
+	glom/libglom/translations_po.h
 
 libglom_data_structure_headers =				\
 	glom/libglom/data_structure/choicevalue.h		\
@@ -112,6 +113,8 @@ libglom_sources =							\
 	glom/libglom/report_builder.h					\
 	glom/libglom/spawn_with_feedback.cc				\
 	glom/libglom/spawn_with_feedback.h				\
+	glom/libglom/translations_po.cc				\
+	glom/libglom/translations_po.h				\
 	glom/libglom/utils.cc						\
 	glom/libglom/utils.h						\
 	glom/libglom/xsl_utils.cc					\
diff --git a/glom/libglom/translations_po.cc b/glom/libglom/translations_po.cc
new file mode 100644
index 0000000..6faa83f
--- /dev/null
+++ b/glom/libglom/translations_po.cc
@@ -0,0 +1,263 @@
+/* Glom
+ *
+ * Copyright (C) 2001-2012 Murray Cumming
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <libglom/translations_po.h>
+
+// To read the .po files
+#include <gettext-po.h>
+#include "config.h" //For HAVE_GETTEXTPO_XERROR
+
+#include <glibmm/convert.h>
+#include <glibmm/i18n.h>
+
+#include <iostream>
+
+/* For really ugly hacks! */
+#include <setjmp.h>
+
+namespace Glom
+{
+
+static jmp_buf jump;
+
+static void show_gettext_error(int severity, const char* filename, const gchar* message)
+{
+  std::ostringstream msg_stream;
+  if(filename)
+    msg_stream << filename << ": ";
+
+  if(message)
+   msg_stream << message;
+
+  switch(severity)
+  {
+    #ifdef PO_SEVERITY_WARNING //This was introduced in libgettext-po some time after gettext version 0.14.5 
+    case PO_SEVERITY_WARNING:
+    {
+      // Show only debug output
+      std::cout << _("Gettext-Warning: ") << msg_stream.str() << std::endl;
+      break;
+    }
+    #endif //PO_SEVERITY_WARNING
+
+
+    #ifdef PO_SEVERITY_ERROR //This was introduced in libgettext-po some time after gettext version 0.14.5 
+    case PO_SEVERITY_ERROR:
+    #endif //PO_SEVERITY_ERROR
+
+    #ifdef PO_SEVERITY_FATAL_ERROR //This was introduced in libgettext-po some time after gettext version 0.14.5 
+    case PO_SEVERITY_FATAL_ERROR:
+    #endif //PO_SEVERITY_FATAL_ERROR
+
+    default:
+    {
+      //TODO: const Glib::ustring msg = Glib::ustring(_("Gettext-Error: ")) + ' ' + msg_stream.str();
+      //Gtk::MessageDialog dlg(msg, false, Gtk::MESSAGE_ERROR);
+      //dlg.run();
+      break;
+    }
+  }   
+}
+
+/*
+ * The exception handling of libgettext-po is very ugly! The following methods are called
+ * if an exception occurs and may not return in case of a fatal exception. We use setjmp
+ * and longjmp to bypass this and return to the caller
+ */
+#ifdef HAVE_GETTEXTPO_XERROR
+static void on_gettextpo_xerror (int severity, po_message_t /* message */, const char *filename, size_t /* lineno */, size_t /* column */,
+  int /* multiline_p */, const char *message_text)
+{
+  show_gettext_error(severity, filename, message_text);
+
+  #ifdef PO_SEVERITY_FATAL_ERROR  //This was introduced in libgettext-po some time after gettext version 0.14.5 
+  if(severity == PO_SEVERITY_FATAL_ERROR)
+    longjmp(jump, 1);
+  #endif //PO_SEVERITY_FATAL_ERROR
+}
+
+static void on_gettextpo_xerror2 (int severity, po_message_t /* message1 */, const char * filename1, size_t /* lineno1 */, size_t /* column1 */,
+  int /* multiline_p1 */, const char *message_text1,
+  po_message_t /* message2 */, const char * /*filename2 */, size_t /* lineno2 */, size_t /* column2 */,
+  int /* multiline_p2 */, const char * /* message_text2 */)
+{
+  show_gettext_error(severity, filename1, message_text1);
+  
+  #ifdef PO_SEVERITY_FATAL_ERROR  //This was introduced in libgettext-po some time after gettext version 0.14.5 
+  if(severity == PO_SEVERITY_FATAL_ERROR)
+    longjmp(jump, 1);
+  #endif //PO_SEVERITY_FATAL_ERROR
+}
+#else //HAVE_GETTEXTPO_XERROR
+static void on_gettextpo_error(int status, int errnum, const char * /* format */, ...)
+{
+  std::cerr << "gettext error (old libgettext-po API): status=" << status << ", errnum=" << errnum << std::endl;
+}
+#endif //HAVE_GETTEXTPO_XERROR
+
+static Glib::ustring get_po_context_for_item(const sharedptr<TranslatableItem>& item)
+{
+  // Note that this context string should use English rather than the translated strings,
+  // or the context would change depending on the locale of the user doing the export:
+  return TranslatableItem::get_translatable_type_name_nontranslated(item->get_translatable_item_type()) + " (" + item->get_name() + ')';
+}
+
+bool write_translations_to_po_file(Document* document, const Glib::ustring& po_file_uri, const Glib::ustring& translation_locale)
+{
+  std::string filename;
+
+  try
+  {
+    filename = Glib::filename_from_uri(po_file_uri);
+  }
+  catch(const Glib::Error& ex)
+  {
+    std::cerr << G_STRFUNC << "Exception when converting URI to filepath: " << ex.what() << std::endl;
+    return false;
+  }
+
+  if(setjmp(jump) != 0)
+    return false;  
+
+  po_file_t po_file = po_file_create();
+  po_message_iterator_t msg_iter = po_message_iterator(po_file, 0);
+
+  Document::type_list_translatables list_layout_items = document->get_translatable_items();
+  for(Document::type_list_translatables::iterator iter = list_layout_items.begin(); iter != list_layout_items.end(); ++iter)
+  {
+    sharedptr<TranslatableItem> item = *iter;
+    if(!item)
+      continue;
+
+    if(item->get_title_original().empty())
+      continue;
+
+    po_message_t msg = po_message_create();
+    po_message_set_msgid(msg, item->get_title_original().c_str());
+    po_message_set_msgstr(msg, item->get_title_translation(translation_locale, false).c_str());
+
+    // Add "context" comments, to uniquely identify similar strings, used in different places,
+    // and to provide a hint for translators.
+    po_message_set_msgctxt(msg, get_po_context_for_item(item).c_str());
+
+    po_message_insert(msg_iter, msg);
+  }
+
+  po_message_iterator_free(msg_iter);
+
+  #ifdef HAVE_GETTEXTPO_XERROR
+  po_xerror_handler error_handler;
+  memset(&error_handler, 0, sizeof(error_handler));
+  error_handler.xerror = &on_gettextpo_xerror;
+  error_handler.xerror2 = &on_gettextpo_xerror2;
+  #else
+  po_error_handler error_handler;
+  memset(&error_handler, 0, sizeof(error_handler));
+  error_handler.error = &on_gettextpo_error;
+  #endif //HAVE_GETTEXTPO_XERROR
+
+  if(po_file_write(po_file, filename.c_str(), &error_handler))
+  {
+    po_file_free(po_file);
+  }
+
+  return true;
+}
+
+bool import_translations_from_po_file(Document* document, const Glib::ustring& po_file_uri, const Glib::ustring& translation_locale)
+{
+  std::string filename;
+
+  try
+  {
+    filename = Glib::filename_from_uri(po_file_uri);
+  }
+  catch(const Glib::Error& ex)
+  {
+    std::cerr << G_STRFUNC << "Exception when converting URI to filepath: " << ex.what() << std::endl;
+    return false;
+  }
+
+  Document::type_list_translatables list_layout_items = document->get_translatable_items();
+  if(list_layout_items.empty())
+    return false;
+
+  if(setjmp(jump) != 0)
+    return false;  
+
+  #ifdef HAVE_GETTEXTPO_XERROR
+  po_xerror_handler error_handler;
+  memset(&error_handler, 0, sizeof(error_handler));
+  error_handler.xerror = &on_gettextpo_xerror;
+  error_handler.xerror2 = &on_gettextpo_xerror2;
+  #else
+  po_error_handler error_handler;
+  memset(&error_handler, 0, sizeof(error_handler));
+  error_handler.error = &on_gettextpo_error;
+  #endif //HAVE_GETTEXTPO_XERROR
+
+  po_file_t po_file = po_file_read(filename.c_str(), &error_handler);
+  if(!po_file)
+  {
+    // error message is already given by error_handle.
+    return false;
+  }
+
+  //Look at each domain (could there be more than one?):
+  const char* const* domains = po_file_domains(po_file);
+  for (int i = 0; domains[i] != 0; ++i)
+  {
+    //Look at each message:
+    po_message_iterator_t iter = po_message_iterator(po_file, domains[i]);
+    po_message_t msg;
+    while ((msg = po_next_message(iter)))
+    {
+      //This message:
+      //TODO: Just use const char* instead of copying it in to a Glib::ustring,
+      //if we have performance problems here:
+      const Glib::ustring msgid = po_message_msgid(msg);
+      const Glib::ustring msgstr = po_message_msgstr(msg);
+      const Glib::ustring msgcontext = po_message_msgctxt(msg);
+
+      //Find the matching item in the list:
+      for(Document::type_list_translatables::iterator iter = list_layout_items.begin(); iter != list_layout_items.end(); ++iter)
+      {
+        sharedptr<TranslatableItem> item = *iter;
+        if(!item)
+         continue;
+
+        if( (item->get_title_original() == msgid) && 
+          (get_po_context_for_item(item) == msgcontext) ) // This is not efficient, but it should be reliable.
+        {
+          item->set_title_translation(translation_locale, msgstr);
+          // Keep examining items, in case there are duplicates. break;
+        }
+      }
+    }
+
+    po_message_iterator_free(iter);
+  }
+
+  po_file_free(po_file);
+
+  return true;
+}
+
+} //namespace Glom
diff --git a/glom/libglom/translations_po.h b/glom/libglom/translations_po.h
new file mode 100644
index 0000000..1207e88
--- /dev/null
+++ b/glom/libglom/translations_po.h
@@ -0,0 +1,35 @@
+/* Glom
+ *
+ * Copyright (C) 2001-2012 Murray Cumming
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GLOM_TRANSLATIONS_PO
+#define GLOM_TRANSLATIONS_PO
+
+#include <libglom/document/document.h>
+
+namespace Glom
+{
+
+bool write_translations_to_po_file(Document* document, const Glib::ustring& po_file_uri, const Glib::ustring& translation_locale);
+
+bool import_translations_from_po_file(Document* document, const Glib::ustring& po_file_uri, const Glib::ustring& translation_locale);
+
+} //namespace Glom
+
+#endif //GLOM_TRANSLATIONS_PO
diff --git a/glom/mode_design/translation/window_translations.cc b/glom/mode_design/translation/window_translations.cc
index f3975c5..aea3fa0 100644
--- a/glom/mode_design/translation/window_translations.cc
+++ b/glom/mode_design/translation/window_translations.cc
@@ -24,6 +24,7 @@
 #include "dialog_copy_translation.h"
 #include <glom/utils_ui.h> //For bold_message()).
 #include <libglom/utils.h>
+#include <libglom/translations_po.h>
 #include <glom/glade_utils.h>
 #include <gtkmm/filefilter.h>
 #include <gtkmm/stock.h>
@@ -32,16 +33,8 @@
 #include <glibmm/i18n.h>
 #include <string.h> // for memset
 
-// To read the .po files
-#include <gettext-po.h>
-#include "config.h" //For HAVE_GETTEXTPO_XERROR
-
 #include <sstream>
 
-/* For really ugly hacks! */
-#include <setjmp.h>
-
-
 namespace Glom
 {
 
@@ -220,132 +213,23 @@ void Window_Translations::load_from_document()
     original_locale_name = _("Unknown");
   m_label_source_locale->set_text(original_locale_name);
 
-  //Add tables:
-  Document::type_listTableInfo tables = document->get_tables();
-  for(Document::type_listTableInfo::const_iterator iter = tables.begin(); iter != tables.end(); ++iter)
+  Document::type_list_translatables list_layout_items = document->get_translatable_items();
+  for(Document::type_list_translatables::iterator iter = list_layout_items.begin(); iter != list_layout_items.end(); ++iter)
   {
-    sharedptr<TableInfo> tableinfo = *iter;
-    const Glib::ustring table_name = tableinfo->get_name();
-
-    //Table title:
-    Gtk::TreeModel::iterator iterTree = m_model->append();
-    Gtk::TreeModel::Row row = *iterTree;
-    row[m_columns.m_col_item] = tableinfo;
-    row[m_columns.m_col_translation] = tableinfo->get_title_translation(m_translation_locale, false);
-    row[m_columns.m_col_parent_table] = Glib::ustring(); //Not used for tables.
-
-    //The table's field titles:
-    Document::type_vec_fields fields = document->get_table_fields(table_name);
-    for(Document::type_vec_fields::iterator iter = fields.begin(); iter != fields.end(); ++iter)
+    sharedptr<TranslatableItem> item = *iter;
+    if(item)
     {
-      sharedptr<Field> field = *iter;
-      if(!field)
+      if(item->get_title_original().empty())
         continue;
-
+      
       Gtk::TreeModel::iterator iterTree = m_model->append();
       Gtk::TreeModel::Row row = *iterTree;
 
-      row[m_columns.m_col_item] = field;
-      row[m_columns.m_col_translation] = field->get_title_translation(m_translation_locale, false);
-      row[m_columns.m_col_parent_table] = table_name;
-
-      //Custom Choices, if any:
-      if(field->get_glom_type() == Field::TYPE_TEXT) //Choices for other field types could not be translated.
-      {
-        Document::type_list_translatables list_choice_items;
-        Document::fill_translatable_custom_choices(field->m_default_formatting, list_choice_items);
-        for(Document::type_list_translatables::iterator iter = list_choice_items.begin(); iter != list_choice_items.end(); ++iter)
-        {
-          sharedptr<TranslatableItem> item = *iter;
-          if(!item)
-            continue;
-
-          if(item->get_title_original().empty())
-            continue;
-
-          Gtk::TreeModel::iterator iterTree = m_model->append();
-          Gtk::TreeModel::Row row = *iterTree;
-
-          row[m_columns.m_col_item] = item;
-          row[m_columns.m_col_translation] = item->get_title_translation(m_translation_locale, false);
-          row[m_columns.m_col_parent_table] = table_name;
-        }
-      }
-    }
-
-    //The table's relationships:
-    Document::type_vec_relationships relationships = document->get_relationships(table_name);
-    for(Document::type_vec_relationships::iterator iter = relationships.begin(); iter != relationships.end(); ++iter)
-    {
-      sharedptr<Relationship> relationship = *iter;
-      if(relationship)
-      {
-        Gtk::TreeModel::iterator iterTree = m_model->append();
-        Gtk::TreeModel::Row row = *iterTree;
-
-        row[m_columns.m_col_item] = relationship;
-        row[m_columns.m_col_translation] = relationship->get_title_translation(m_translation_locale, false);
-        row[m_columns.m_col_parent_table] = table_name;
-      }
-    }
-
-    //The table's report titles:
-    Document::type_listReports listReports = document->get_report_names(table_name);
-    for(Document::type_listReports::iterator iter = listReports.begin(); iter != listReports.end(); ++iter)
-    {
-      const Glib::ustring report_name = *iter;
-      sharedptr<Report> report = document->get_report(table_name, report_name);
-      if(report)
-      {
-        //Translatable report title:
-        Gtk::TreeModel::iterator iterTree = m_model->append();
-        Gtk::TreeModel::Row row = *iterTree;
-
-        row[m_columns.m_col_item] = report;
-        row[m_columns.m_col_translation] = report->get_title_translation(m_translation_locale, false);
-        row[m_columns.m_col_parent_table] = table_name;
-
-        //Translatable report items:
-        Document::type_list_translatables list_layout_items = document->get_translatable_report_items(table_name, report_name);
-        for(Document::type_list_translatables::iterator iter = list_layout_items.begin(); iter != list_layout_items.end(); ++iter)
-        {
-          sharedptr<TranslatableItem> item = *iter;
-          if(item)
-          {
-            if(!(item->get_title_original().empty()))
-            {
-              Gtk::TreeModel::iterator iterTree = m_model->append();
-              Gtk::TreeModel::Row row = *iterTree;
-
-              row[m_columns.m_col_item] = item;
-              row[m_columns.m_col_translation] = item->get_title_translation(m_translation_locale, false);
-              row[m_columns.m_col_parent_table] = table_name;
-            }
-          }
-        }
-      }
-    }
-
-    //The table's translatable layout items:
-    Document::type_list_translatables list_layout_items = document->get_translatable_layout_items(table_name);
-    for(Document::type_list_translatables::iterator iter = list_layout_items.begin(); iter != list_layout_items.end(); ++iter)
-    {
-      sharedptr<TranslatableItem> item = *iter;
-      if(item)
-      {
-        if(!(item->get_title_original().empty()))
-        {
-          Gtk::TreeModel::iterator iterTree = m_model->append();
-          Gtk::TreeModel::Row row = *iterTree;
-
-          row[m_columns.m_col_item] = item;
-          row[m_columns.m_col_translation] = item->get_title_translation(m_translation_locale, false);
-          row[m_columns.m_col_parent_table] = table_name;
-        }
-      }
+      row[m_columns.m_col_item] = item;
+      row[m_columns.m_col_translation] = item->get_title_translation(m_translation_locale, false);
+      //row[m_columns.m_col_parent_table] = table_name;
     }
-
-  } //for
+  }
 
   m_treeview_modified = false;
 }
@@ -438,95 +322,8 @@ void Window_Translations::on_treeview_edited(const Glib::ustring& /* path */, co
   m_treeview_modified = true;
 }
 
-static jmp_buf jump;
-
-static void show_gettext_error(int severity, const char* filename, const gchar* message)
-{
-  std::ostringstream msg_stream;
-  if(filename)
-    msg_stream << filename << ": ";
-
-  if(message)
-   msg_stream << message;
-
-  switch(severity)
-  {
-    #ifdef PO_SEVERITY_WARNING //This was introduced in libgettext-po some time after gettext version 0.14.5 
-    case PO_SEVERITY_WARNING:
-    {
-      // Show only debug output
-      std::cout << _("Gettext-Warning: ") << msg_stream.str() << std::endl;
-      break;
-    }
-    #endif //PO_SEVERITY_WARNING
-
-
-    #ifdef PO_SEVERITY_ERROR //This was introduced in libgettext-po some time after gettext version 0.14.5 
-    case PO_SEVERITY_ERROR:
-    #endif //PO_SEVERITY_ERROR
-
-    #ifdef PO_SEVERITY_FATAL_ERROR //This was introduced in libgettext-po some time after gettext version 0.14.5 
-    case PO_SEVERITY_FATAL_ERROR:
-    #endif //PO_SEVERITY_FATAL_ERROR
-
-    default:
-    {
-      Glib::ustring msg = Glib::ustring(_("Gettext-Error: ")) + ' ' + msg_stream.str();
-      Gtk::MessageDialog dlg(msg, false, Gtk::MESSAGE_ERROR);
-      dlg.run();
-      break;
-    }
-  }   
-}
-
-/*
- * The exception handling of libgettext-po is very ugly! The following methods are called
- * if an exception occurs and may not return in case of a fatal exception. We use setjmp
- * and longjmp to bypass this and return to the caller
- */
-#ifdef HAVE_GETTEXTPO_XERROR
-static void on_gettextpo_xerror (int severity, po_message_t /* message */, const char *filename, size_t /* lineno */, size_t /* column */,
-                  int /* multiline_p */, const char *message_text)
-{
-  show_gettext_error(severity, filename, message_text);
-
-  #ifdef PO_SEVERITY_FATAL_ERROR  //This was introduced in libgettext-po some time after gettext version 0.14.5 
-  if(severity == PO_SEVERITY_FATAL_ERROR)
-    longjmp(jump, 1);
-  #endif //PO_SEVERITY_FATAL_ERROR
-}
-
-static void on_gettextpo_xerror2 (int severity, po_message_t /* message1 */, const char * filename1, size_t /* lineno1 */, size_t /* column1 */,
-                   int /* multiline_p1 */, const char *message_text1,
-                   po_message_t /* message2 */, const char * /*filename2 */, size_t /* lineno2 */, size_t /* column2 */,
-                   int /* multiline_p2 */, const char * /* message_text2 */)
-{
-  show_gettext_error(severity, filename1, message_text1);
-  
-  #ifdef PO_SEVERITY_FATAL_ERROR  //This was introduced in libgettext-po some time after gettext version 0.14.5 
-  if(severity == PO_SEVERITY_FATAL_ERROR)
-    longjmp(jump, 1);
-  #endif //PO_SEVERITY_FATAL_ERROR
-}
-#else //HAVE_GETTEXTPO_XERROR
-static void on_gettextpo_error(int status, int errnum, const char * /* format */, ...)
-{
-  std::cerr << "gettext error (old libgettext-po API): status=" << status << ", errnum=" << errnum << std::endl;
-}
-#endif //HAVE_GETTEXTPO_XERROR
-
-Glib::ustring Window_Translations::get_po_context_for_item(const sharedptr<TranslatableItem>& item)
-{
-  // Note that this context string should use English rather than the translated strings,
-  // or the context would change depending on the locale of the user doing the export:
-  return TranslatableItem::get_translatable_type_name_nontranslated(item->get_translatable_item_type()) + " (" + item->get_name() + ')';
-}
-
 void Window_Translations::on_button_export()
-{
-  if(setjmp(jump) != 0)
-    return;  
-  
+{ 
   //Show the file-chooser dialog, to select an output .po file:
   Gtk::FileChooserDialog file_dlg(_("Choose .po File Name"), Gtk::FILE_CHOOSER_ACTION_SAVE);
   file_dlg.set_transient_for(*this);
@@ -539,74 +336,34 @@ void Window_Translations::on_button_export()
   file_dlg.add_filter(filter);
 
   file_dlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
-  file_dlg.add_button(_("Export"), Gtk::RESPONSE_OK);  
+  file_dlg.add_button(_("Export"), Gtk::RESPONSE_OK); 
   
   const int result = file_dlg.run();
-  if(result == Gtk::RESPONSE_OK)
-  {
-      std::string filename = file_dlg.get_filename();
-      if(filename.empty())
-        return;
-
-      //Enforce the file extension:
-      const std::string extension = ".po";
-      bool add_extension = false;
-      if(filename.size() <= extension.size())
-         add_extension = true;
-      else if(filename.substr(filename.size() - extension.size()) != extension)
-         add_extension = true;
-
-      if(add_extension)
-        filename += extension;
-
-
-      po_file_t po_file = po_file_create();
-      po_message_iterator_t msg_iter = po_message_iterator(po_file, 0);
-      
-      for(Gtk::TreeModel::iterator iter = m_model->children().begin(); iter != m_model->children().end(); ++iter)
-      {
-        Gtk::TreeModel::Row row = *iter;
-
-        sharedptr<TranslatableItem> item = row[m_columns.m_col_item];
-        if(item)
-        {
-          po_message_t msg = po_message_create();
-          po_message_set_msgid(msg, item->get_title_original().c_str());
-          po_message_set_msgstr(msg, item->get_title_translation(m_translation_locale, false).c_str());
-
-          // Add "context" comments, to uniquely identify similar strings, used in different places,
-          // and to provide a hint for translators.
-          po_message_set_msgctxt(msg, get_po_context_for_item(item).c_str());
-
-          po_message_insert(msg_iter, msg);
-        }
-      }
-
-      po_message_iterator_free(msg_iter);
+  if(result != Gtk::RESPONSE_OK)
+    return;
 
-      #ifdef HAVE_GETTEXTPO_XERROR
-      po_xerror_handler error_handler;
-      memset(&error_handler, 0, sizeof(error_handler));
-      error_handler.xerror = &on_gettextpo_xerror;
-      error_handler.xerror2 = &on_gettextpo_xerror2;
-      #else
-      po_error_handler error_handler;
-      memset(&error_handler, 0, sizeof(error_handler));
-      error_handler.error = &on_gettextpo_error;
-      #endif //HAVE_GETTEXTPO_XERROR
+  Glib::ustring uri = file_dlg.get_uri();
+  if(uri.empty())
+    return;
 
-      if(po_file_write(po_file, filename.c_str(), &error_handler))
-      {
-        po_file_free(po_file);
-      }
-   }
+  save_to_document();
+  
+  //Enforce the file extension:
+  const Glib::ustring extension = ".po";
+  bool add_extension = false;
+  if(uri.size() <= extension.size())
+    add_extension = true;
+  else if(uri.substr(uri.size() - extension.size()) != extension)
+    add_extension = true;
+
+  if(add_extension)
+    uri += extension;
+
+  Glom::write_translations_to_po_file(get_document(), uri, m_translation_locale);
 }
 
 void Window_Translations::on_button_import()
 {
-  if(setjmp(jump) != 0)
-    return;
-
   Gtk::FileChooserDialog file_dlg(_("Choose .po File Name"), Gtk::FILE_CHOOSER_ACTION_OPEN);
   file_dlg.set_transient_for(*this);
 
@@ -621,72 +378,18 @@ void Window_Translations::on_button_import()
   //Note to translators: "Import" here is an action verb - it's a button. 
   file_dlg.add_button(_("Import"), Gtk::RESPONSE_OK);
   
-  int result = file_dlg.run();
-  if(result == Gtk::RESPONSE_OK)
-  {
-      // We cannot use an uri here:
-      const std::string filename = file_dlg.get_filename();
-      if(filename.empty())
-        return;
-
-      #ifdef HAVE_GETTEXTPO_XERROR
-      po_xerror_handler error_handler;
-      memset(&error_handler, 0, sizeof(error_handler));
-      error_handler.xerror = &on_gettextpo_xerror;
-      error_handler.xerror2 = &on_gettextpo_xerror2;
-      #else
-      po_error_handler error_handler;
-      memset(&error_handler, 0, sizeof(error_handler));
-      error_handler.error = &on_gettextpo_error;
-      #endif //HAVE_GETTEXTPO_XERROR
-
-      po_file_t po_file = po_file_read(filename.c_str(), &error_handler);
-      if(!po_file)
-      {
-        // error message is already given by error_handle.
-        return;
-      }
-
-      //Look at each domain (could there be more than one?):
-      const char* const* domains = po_file_domains(po_file);
-      for (int i = 0; domains[i] != 0; ++i)
-      {
-        //Look at each message:
-        po_message_iterator_t iter = po_message_iterator(po_file, domains[i]);
-        po_message_t msg;
-        while ((msg = po_next_message(iter)))
-        {
-          //This message:
-          //TODO: Just use const char* instead of copying it in to a Glib::ustring,
-          //if we have performance problems here:
-          const Glib::ustring msgid = po_message_msgid(msg);
-          const Glib::ustring msgstr = po_message_msgstr(msg);
-          const Glib::ustring msgcontext = po_message_msgctxt(msg);
-
-          //Find the matching entry in the TreeModel:
-          for(Gtk::TreeModel::iterator iter = m_model->children().begin(); iter != m_model->children().end(); ++iter)
-          {
-            Gtk::TreeModel::Row row = *iter;
-
-            sharedptr<TranslatableItem> item = row[m_columns.m_col_item];
-            if(item)
-            {
-              if( (item->get_title_original() == msgid) && 
-                  (get_po_context_for_item(item) == msgcontext) ) // This is not efficient, but it should be reliable.
-              {
-                item->set_title_translation(m_translation_locale, msgstr);
-                // Keep examining items, in case there are duplicates. break;
-              }
-            }
-          }
-        }
-        po_message_iterator_free(iter);
-      }
+  const int result = file_dlg.run();
+  if(result != Gtk::RESPONSE_OK)
+    return;
 
-      po_file_free(po_file);
+  const std::string uri = file_dlg.get_uri();
+  if(uri.empty())
+    return;
 
-      save_to_document();
-   }
+  Glom::import_translations_from_po_file(get_document(), uri, m_translation_locale);
+  
+  //Show the changed document in the UI:
+  load_from_document();
 }
 
 } //namespace Glom
diff --git a/glom/mode_design/translation/window_translations.h b/glom/mode_design/translation/window_translations.h
index ccf6994..bfbbcbd 100644
--- a/glom/mode_design/translation/window_translations.h
+++ b/glom/mode_design/translation/window_translations.h
@@ -76,11 +76,11 @@ private:
   public:
 
     ModelColumns()
-    { add(m_col_item); add(m_col_translation); add(m_col_parent_table); }
+    { add(m_col_item); add(m_col_translation); }
 
     Gtk::TreeModelColumn< sharedptr<TranslatableItem> > m_col_item; //The table name, field name, etc.
     Gtk::TreeModelColumn<Glib::ustring> m_col_translation;
-    Gtk::TreeModelColumn<Glib::ustring> m_col_parent_table; //Not shown.
+    //Gtk::TreeModelColumn<Glib::ustring> m_col_parent_table; //Not shown.
   };
 
   ModelColumns m_columns;
diff --git a/po/POTFILES.in b/po/POTFILES.in
index a4209ba..6cea3c2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -52,11 +52,13 @@ glom/libglom/document/bakery/view/view.cc
 glom/libglom/document/bakery/view/view_composite.cc
 glom/libglom/gst-package.c
 glom/libglom/spawn_with_feedback.cc
+glom/libglom/translations_po.cc
 glom/libglom/utils.cc
 glom/libglom/db_utils.cc
 glom/libglom/report_builder.cc
 glom/libglom/xsl_utils.cc
 glom/glom_create_from_example.cc
+glom/glom_export_po.cc
 glom/glom_test_connection.cc
 glom/main.cc
 glom/mode_data/box_data_calendar_related.cc
diff --git a/tests/test_document_load_translations.cc b/tests/test_document_load_translations.cc
index e6b44a1..a3e7d35 100644
--- a/tests/test_document_load_translations.cc
+++ b/tests/test_document_load_translations.cc
@@ -70,6 +70,39 @@ private:
   Glib::ustring m_title;
 };
 
+/** A predicate for use with std::find_if() to find a LayoutItem of a particular type.
+ */
+template<class T_Element, class T_TypeToFind>
+class predicate_ItemHasType
+{
+public:
+  predicate_ItemHasType()
+  {
+  }
+
+  virtual ~predicate_ItemHasType()
+  {
+  }
+
+  bool operator() (const Glom::sharedptr<T_Element>& element)
+  {
+    Glom::sharedptr<T_TypeToFind> derived = Glom::sharedptr<T_TypeToFind>::cast_dynamic(element);
+    if(derived)
+      return true;
+    else
+      return false;
+  }
+
+  bool operator() (const Glom::sharedptr<const T_Element>& element)
+  {
+     Glom::sharedptr<const T_TypeToFind> derived = Glom::sharedptr<const T_TypeToFind>::cast_dynamic(element);
+    if(derived)
+      return true;
+    else
+      return false;
+  }
+};
+
 
 template<typename T_Container>
 typename T_Container::value_type get_titled(const T_Container& container, const Glib::ustring& title)
@@ -87,6 +120,22 @@ typename T_Container::value_type get_titled(const T_Container& container, const
   return result;
 }
 
+template<typename T_Container, typename T_TypeToFind>
+bool contains_item_type(const T_Container& container)
+{
+  typedef typename T_Container::value_type type_sharedptr;
+  type_sharedptr result;
+
+  typedef typename T_Container::value_type::object_type type_item;
+  typename T_Container::const_iterator iter =
+    std::find_if(container.begin(), container.end(),
+      predicate_ItemHasType<type_item, T_TypeToFind>());
+  if(iter != container.end())
+    result = *iter;
+
+  return result;
+}
+
 static Glom::sharedptr<const Glom::LayoutItem_Field> get_field_on_layout(const Glom::Document& document, const Glib::ustring& layout_table_name, const Glib::ustring& table_name, const Glib::ustring& field_name)
 {
   const Glom::Document::type_list_layout_groups groups = 
@@ -260,6 +309,35 @@ int main()
   g_assert(print_layout);
   check_title(print_layout, "Contact Details", "Kontakt Details" );
 
+  //Check the whole list of translatable items:
+  Glom::Document::type_list_translatables list_layout_items = document.get_translatable_items();
+  g_assert(!list_layout_items.empty());
+  const bool contains_tableinfo =
+    contains_item_type<Glom::Document::type_list_translatables, Glom::TableInfo>(list_layout_items);
+  g_assert( contains_tableinfo );
+  const bool contains_layoutitem =
+    contains_item_type<Glom::Document::type_list_translatables, Glom::LayoutItem>(list_layout_items);
+  g_assert( contains_layoutitem );
+  /*
+  const bool contains_layoutitemfield =
+    contains_item_type<Glom::Document::type_list_translatables, Glom::LayoutItem_Field>(list_layout_items);
+  g_assert( contains_layoutitemfield );
+  */
+  const bool contains_relationship =
+    contains_item_type<Glom::Document::type_list_translatables, Glom::Relationship>(list_layout_items);
+  g_assert( contains_relationship );
+  const bool contains_field =
+    contains_item_type<Glom::Document::type_list_translatables, Glom::Field>(list_layout_items);
+  g_assert( contains_field );
+  const bool contains_choicevalue =
+    contains_item_type<Glom::Document::type_list_translatables, Glom::ChoiceValue>(list_layout_items);
+  g_assert( contains_choicevalue );
+  const bool contains_customtitle =
+    contains_item_type<Glom::Document::type_list_translatables, Glom::CustomTitle>(list_layout_items);
+  g_assert( contains_customtitle );
+  const bool contains_report =
+    contains_item_type<Glom::Document::type_list_translatables, Glom::Report>(list_layout_items);
+  g_assert( contains_report );
 
   Glom::libglom_deinit();
 
diff --git a/tests/translations_po/data/test.po b/tests/translations_po/data/test.po
new file mode 100644
index 0000000..c063e65
--- /dev/null
+++ b/tests/translations_po/data/test.po
@@ -0,0 +1,1163 @@
+msgctxt "Table (accommodation)"
+msgid "Accommodation"
+msgstr "Unterkunft"
+
+msgctxt "Field (accommodation_id)"
+msgid "Accommodation ID"
+msgstr "Unterkunft ID"
+
+msgctxt "Field (description)"
+msgid "Description"
+msgstr "Beschreibung"
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr "Kommentare"
+
+msgctxt "Field (address_street)"
+msgid "Street"
+msgstr "Strasse"
+
+msgctxt "Field (address_town)"
+msgid "Town"
+msgstr "Ort"
+
+msgctxt "Field (address_county)"
+msgid "County"
+msgstr "Bundesland/Kanton"
+
+msgctxt "Field (address_country)"
+msgid "Country"
+msgstr "Land"
+
+msgctxt "Field (address_postcode)"
+msgid "Postcode"
+msgstr "Postleitzahl"
+
+msgctxt "Field (contact_id)"
+msgid "Contact ID"
+msgstr "Kontakt ID"
+
+msgctxt "Field (name)"
+msgid "Name"
+msgstr ""
+
+msgctxt "Relationship (contacts)"
+msgid "Contacts"
+msgstr "Kontakte"
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr ""
+
+msgctxt "Layout Group (address)"
+msgid "Address"
+msgstr "Addresse"
+
+msgctxt "Layout Group (contact)"
+msgid "Contact"
+msgstr "Kontakt"
+
+msgctxt "Table (cars)"
+msgid "Cars"
+msgstr "Autos"
+
+msgctxt "Field (car_id)"
+msgid "Car ID"
+msgstr "Auto ID"
+
+msgctxt "Field (manufacturer)"
+msgid "Manufacturer"
+msgstr "Hersteller"
+
+msgctxt "Field (model)"
+msgid "Model"
+msgstr "Model"
+
+msgctxt "Field (registration)"
+msgid "Registration"
+msgstr "Kennzeichnung"
+
+msgctxt "Field (description)"
+msgid "Description"
+msgstr "Beschreibung"
+
+msgctxt "Field (comment)"
+msgid "Comment"
+msgstr "Kommentar"
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr "bersicht"
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr "Details"
+
+msgctxt "Table (characters)"
+msgid "Characters"
+msgstr "Besetzung"
+
+msgctxt "Field (character_id)"
+msgid "Cast ID"
+msgstr "Besetzung ID"
+
+msgctxt "Field (character)"
+msgid "Character"
+msgstr "Charakter"
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr "Kommentar"
+
+msgctxt "Field (contact_id)"
+msgid "Contact ID"
+msgstr "Kontakt ID"
+
+msgctxt "Field (mainpart)"
+msgid "Main Part"
+msgstr "Hauptrolle"
+
+msgctxt "Relationship (contacts_actor)"
+msgid "Actor"
+msgstr "Schauspieler"
+
+msgctxt "Relationship (scenes)"
+msgid "Scenes"
+msgstr ""
+
+msgctxt "Report (cast_list)"
+msgid "Cast List"
+msgstr "Besetzungsliste"
+
+msgctxt "Custom Title ()"
+msgid "Actor"
+msgstr "Schauspieler"
+
+msgctxt "Custom Title ()"
+msgid "Agent"
+msgstr ""
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr "bersicht"
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr "Details"
+
+msgctxt "Layout Group (actor)"
+msgid "Actor"
+msgstr "Schauspieler"
+
+msgctxt "Custom Title ()"
+msgid "Actor's Contact ID"
+msgstr "Schauspieler Kontakt ID"
+
+msgctxt "Custom Title ()"
+msgid "Actor's Name"
+msgstr "Schauspieler Name"
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr ""
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr ""
+
+msgctxt "Layout Group (actor)"
+msgid "Actor"
+msgstr ""
+
+msgctxt "Custom Title ()"
+msgid "Actor's Name"
+msgstr "Schauspieler Name"
+
+msgctxt "Custom Title ()"
+msgid "Actor"
+msgstr ""
+
+msgctxt "Table (companies)"
+msgid "Companies"
+msgstr "Firmen"
+
+msgctxt "Field (company_id)"
+msgid "Company ID"
+msgstr "Firma ID"
+
+msgctxt "Field (name)"
+msgid "Name"
+msgstr ""
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr "Kommentar"
+
+msgctxt "Field (description)"
+msgid "Description"
+msgstr "Beschreibung"
+
+msgctxt "Field (logo)"
+msgid "Logo"
+msgstr ""
+
+msgctxt "Field (website)"
+msgid "Web Site"
+msgstr "Website"
+
+msgctxt "Relationship (staff)"
+msgid "Staff"
+msgstr "Angestellte"
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr "bersicht"
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr "Details"
+
+msgctxt "Table (contacts)"
+msgid "Contacts"
+msgstr "Kontakte"
+
+msgctxt "Field (contact_id)"
+msgid "Contact ID"
+msgstr "Kontakt ID"
+
+msgctxt "Field (name_first)"
+msgid "First Name"
+msgstr "Vorname"
+
+msgctxt "Field (name_middle)"
+msgid "Second Name"
+msgstr "Zweiter Name"
+
+msgctxt "Field (name_last)"
+msgid "Last Name"
+msgstr "Familiename"
+
+msgctxt "Field (name_title)"
+msgid "Title"
+msgstr "Titel"
+
+msgctxt "Field Choice ()"
+msgid "Mr"
+msgstr ""
+
+msgctxt "Field Choice ()"
+msgid "Ms"
+msgstr ""
+
+msgctxt "Field Choice ()"
+msgid "Mrs"
+msgstr ""
+
+msgctxt "Field Choice ()"
+msgid "Miss"
+msgstr ""
+
+msgctxt "Field Choice ()"
+msgid "Dr"
+msgstr ""
+
+msgctxt "Field Choice ()"
+msgid "Prof"
+msgstr ""
+
+msgctxt "Field (address_street2)"
+msgid "Street (line 2)"
+msgstr "Strasse (2)"
+
+msgctxt "Field (address_town)"
+msgid "Town"
+msgstr "Ort"
+
+msgctxt "Field (address_state)"
+msgid "State"
+msgstr "Bundesland/Kanton"
+
+msgctxt "Field (address_country)"
+msgid "Country"
+msgstr "Land"
+
+msgctxt "Field Choice ()"
+msgid "Germany"
+msgstr ""
+
+msgctxt "Field Choice ()"
+msgid "United Kingdom"
+msgstr ""
+
+msgctxt "Field Choice ()"
+msgid "USA"
+msgstr ""
+
+msgctxt "Field Choice ()"
+msgid "France"
+msgstr ""
+
+msgctxt "Field Choice ()"
+msgid "Spain"
+msgstr ""
+
+msgctxt "Field (address_postcode)"
+msgid "Postcode"
+msgstr "Postleitzahl"
+
+msgctxt "Field (date_of_birth)"
+msgid "Date Of Birth"
+msgstr "Geburtsdatum"
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr "Kommentare"
+
+msgctxt "Field (name_full)"
+msgid "Full Name"
+msgstr "Vollstndiger Name"
+
+msgctxt "Field (company_id)"
+msgid "Company ID"
+msgstr "Firma ID"
+
+msgctxt "Field (picture)"
+msgid "Picture"
+msgstr "Bild"
+
+msgctxt "Field (tel_home)"
+msgid "Home Telephone"
+msgstr "Telefon (Privat)"
+
+msgctxt "Field (tel_mobile)"
+msgid "Mobile Telephone"
+msgstr "Telefon (Handy)"
+
+msgctxt "Field (tel_fax)"
+msgid "Fax"
+msgstr ""
+
+msgctxt "Field (tel_work)"
+msgid "Work Telephone"
+msgstr "Telefon (Buro)"
+
+msgctxt "Field (email)"
+msgid "Email"
+msgstr "E-Mail"
+
+msgctxt "Field (address_street1)"
+msgid "Street (line 1)"
+msgstr "Strasse (1)"
+
+msgctxt "Field (website)"
+msgid "Web Site"
+msgstr "Website"
+
+msgctxt "Field (agent_id)"
+msgid "Agent ID"
+msgstr ""
+
+msgctxt "Field (stagename)"
+msgid "Stagename"
+msgstr "Knstlername"
+
+msgctxt "Relationship (company)"
+msgid "Company"
+msgstr "Firma"
+
+msgctxt "Relationship (agent)"
+msgid "Agent"
+msgstr ""
+
+msgctxt "Relationship (crew)"
+msgid "Crew"
+msgstr ""
+
+msgctxt "Relationship (cast)"
+msgid "Cast"
+msgstr ""
+
+msgctxt "Report (by_country)"
+msgid "Contacts By Country"
+msgstr ""
+
+msgctxt "Report (by_country_by_town)"
+msgid "By Country, By Town"
+msgstr ""
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr "bersicht"
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr "Details"
+
+msgctxt "Layout Group (name)"
+msgid "Name"
+msgstr "Name"
+
+msgctxt "Layout Group (company)"
+msgid "Company"
+msgstr "Firma"
+
+msgctxt "Layout Group (address)"
+msgid "Address"
+msgstr "Addresse"
+
+msgctxt "Layout Group (telephone)"
+msgid "Telephone"
+msgstr "Telefon"
+
+msgctxt "Layout Group (agent)"
+msgid "Agent"
+msgstr "Agent"
+
+msgctxt "Custom Title ()"
+msgid "Company Name"
+msgstr ""
+
+msgctxt "Table (costume)"
+msgid "Costume"
+msgstr ""
+
+msgctxt "Field (costume_id)"
+msgid "Costume ID"
+msgstr ""
+
+msgctxt "Field (description)"
+msgid "Description"
+msgstr ""
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr ""
+
+msgctxt "Field (name)"
+msgid "Name"
+msgstr ""
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr ""
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr ""
+
+msgctxt "Table (crew)"
+msgid "Crew"
+msgstr ""
+
+msgctxt "Field (crew_id)"
+msgid "Crew ID"
+msgstr ""
+
+msgctxt "Field (description)"
+msgid "Description"
+msgstr "Beschreibung"
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr "Kommentar"
+
+msgctxt "Field (dept_id)"
+msgid "Department ID"
+msgstr "Abteilung ID"
+
+msgctxt "Field (contact_id)"
+msgid "Contact ID"
+msgstr "Kontakt ID"
+
+msgctxt "Relationship (contacts)"
+msgid "Contacts"
+msgstr "Kontakten"
+
+msgctxt "Relationship (departments)"
+msgid "Departments"
+msgstr "Abteilungen"
+
+msgctxt "Relationship (scenes)"
+msgid "Scenes"
+msgstr "Szenen"
+
+msgctxt "Report (crew_list)"
+msgid "Crew List"
+msgstr "TestResult2"
+
+msgctxt "Custom Title ()"
+msgid "Department Name"
+msgstr "Abteilungsname"
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr "bersicht"
+
+msgctxt "Layout Group (department)"
+msgid "Department"
+msgstr "Abteilung"
+
+msgctxt "Layout Group (contact)"
+msgid "Contact"
+msgstr "Kontakt"
+
+msgctxt "Layout Group (address)"
+msgid "Address"
+msgstr "Addresse"
+
+msgctxt "Layout Group (agent)"
+msgid "Agent"
+msgstr "Agent"
+
+msgctxt "Custom Title ()"
+msgid "Department Name"
+msgstr "Abteilungsname"
+
+msgctxt "Table (deliveries)"
+msgid "Deliveries"
+msgstr "Lieferungen"
+
+msgctxt "Field (delivery_id)"
+msgid "Delivery ID"
+msgstr "Lieferung ID"
+
+msgctxt "Field (arrival_date)"
+msgid "Arrival Date"
+msgstr "Lieferungsdatum"
+
+msgctxt "Field (departure_contact_id)"
+msgid "Contact ID"
+msgstr "Kontakt ID"
+
+msgctxt "Field (arrival_contact_id)"
+msgid "Contact ID"
+msgstr "Kontakt ID"
+
+msgctxt "Field (arrival_time)"
+msgid "Arrival Time"
+msgstr "Lieferungszeit"
+
+msgctxt "Field (arrival_place)"
+msgid "Arrival Place"
+msgstr "Lieferungsaddresse"
+
+msgctxt "Field (departure_time)"
+msgid "Departure Time"
+msgstr "Sendungszeit"
+
+msgctxt "Field (departure_date)"
+msgid "Departure Date"
+msgstr "Sendungsdatum"
+
+msgctxt "Field (departure_place)"
+msgid "Departure Place"
+msgstr "Sendungsaddresse"
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr "Kommentare"
+
+msgctxt "Field (description)"
+msgid "Description"
+msgstr "Beschreibung"
+
+msgctxt "Relationship (departure_contact)"
+msgid "Departure Contact"
+msgstr "Sendungskontakt"
+
+msgctxt "Relationship (arrival_contact)"
+msgid "Arrival Contact"
+msgstr "Empfaengerkontakt"
+
+msgctxt "Custom Title ()"
+msgid "Departure Name"
+msgstr "Sendername"
+
+msgctxt "Custom Title ()"
+msgid "Arrival Name"
+msgstr "Empfngerkontakt"
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr ""
+
+msgctxt "Layout Group (departure)"
+msgid "Departure"
+msgstr "Departure"
+
+msgctxt "Layout Group (arrival)"
+msgid "Arrival"
+msgstr "Arrival"
+
+msgctxt "Table (departments)"
+msgid "Departments"
+msgstr "Abteilungen"
+
+msgctxt "Field (departments_id)"
+msgid "Department ID"
+msgstr "Abteilung ID"
+
+msgctxt "Field (name)"
+msgid "Name"
+msgstr ""
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr "Kommentar"
+
+msgctxt "Relationship (department_crew)"
+msgid "Department Crew"
+msgstr "Abteilung Crew"
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr "bersicht"
+
+msgctxt "Table (equipment)"
+msgid "Equipment"
+msgstr ""
+
+msgctxt "Field (equipment_id)"
+msgid "Equipment ID"
+msgstr ""
+
+msgctxt "Field (description)"
+msgid "Description"
+msgstr "Beschreibung"
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr "Kommentar"
+
+msgctxt "Relationship (scenes)"
+msgid "Scenes"
+msgstr "Szenen"
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr "bersicht"
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr "Details"
+
+msgctxt "Table (journeys)"
+msgid "Journeys"
+msgstr "Fahrten"
+
+msgctxt "Field (journey_id)"
+msgid "Journey ID"
+msgstr "Fahrt ID"
+
+msgctxt "Field (comment)"
+msgid "Comment"
+msgstr "Kommentar"
+
+msgctxt "Field (description)"
+msgid "Description"
+msgstr "Beschreibung"
+
+msgctxt "Field (arrival_date)"
+msgid "Arrival Date"
+msgstr ""
+
+msgctxt "Field (arrival_time)"
+msgid "Arrival Time"
+msgstr ""
+
+msgctxt "Field (arrival_place)"
+msgid "Arrival Place"
+msgstr ""
+
+msgctxt "Field (departure_date)"
+msgid "Departure Date"
+msgstr ""
+
+msgctxt "Field (departure_time)"
+msgid "Departure Time"
+msgstr ""
+
+msgctxt "Field (departure_place)"
+msgid "Departure Place"
+msgstr ""
+
+msgctxt "Field (contact_id)"
+msgid "Contact ID"
+msgstr "Kontakt ID"
+
+msgctxt "Relationship (contacts)"
+msgid "Contacts"
+msgstr "Kontakte"
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr ""
+
+msgctxt "Layout Group (person)"
+msgid "Person"
+msgstr "Person"
+
+msgctxt "Layout Group (departure)"
+msgid "Departure"
+msgstr "Departure"
+
+msgctxt "Layout Group (arrival)"
+msgid "Arrival"
+msgstr "Arrival"
+
+msgctxt "Table (locations)"
+msgid "Locations"
+msgstr ""
+
+msgctxt "Field (location_id)"
+msgid "Location ID"
+msgstr ""
+
+msgctxt "Field (name)"
+msgid "Name"
+msgstr ""
+
+msgctxt "Field (address_street)"
+msgid "Street"
+msgstr "Strasse"
+
+msgctxt "Field (address_town)"
+msgid "Town"
+msgstr "Stadt"
+
+msgctxt "Field (address_county)"
+msgid "County"
+msgstr "Land"
+
+msgctxt "Field (address_country)"
+msgid "Country"
+msgstr "Staat"
+
+msgctxt "Field (address_postcode)"
+msgid "Postcode"
+msgstr "Postleitzahl"
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr "Kommentar"
+
+msgctxt "Field (contact_id)"
+msgid "Contact ID"
+msgstr ""
+
+msgctxt "Field (rent)"
+msgid "Rent"
+msgstr ""
+
+msgctxt "Relationship (scenes)"
+msgid "Scenes"
+msgstr "Szenen"
+
+msgctxt "Relationship (contacts)"
+msgid "Contacts"
+msgstr ""
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr ""
+
+msgctxt "Layout Group (address)"
+msgid "Address"
+msgstr "Addresse"
+
+msgctxt "Layout Group (contact_person)"
+msgid "Contact Person"
+msgstr "Contact Person"
+
+msgctxt "Table (props)"
+msgid "Props"
+msgstr ""
+
+msgctxt "Field (prop_id)"
+msgid "Prop ID"
+msgstr ""
+
+msgctxt "Field (description)"
+msgid "Description"
+msgstr ""
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr ""
+
+msgctxt "Field (name)"
+msgid "Name"
+msgstr ""
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr ""
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr ""
+
+msgctxt "Table (scene_cast)"
+msgid "Scene Cast"
+msgstr "Szene Besetzung"
+
+msgctxt "Field (scene_cast_id)"
+msgid "Scene Cast ID"
+msgstr "Szene Besetzung ID"
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr "Kommentar"
+
+msgctxt "Field (cast_id)"
+msgid "Cast ID"
+msgstr "Besetzung ID"
+
+msgctxt "Field (scene_id)"
+msgid "Scene ID"
+msgstr "Szene ID"
+
+msgctxt "Relationship (cast)"
+msgid "Cast"
+msgstr "Besetzung"
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr ""
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr ""
+
+msgctxt "Table (scene_costume)"
+msgid "Scene Costume"
+msgstr ""
+
+msgctxt "Field (scene_costume_id)"
+msgid "Scene Costume ID"
+msgstr ""
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr ""
+
+msgctxt "Field (scene_id)"
+msgid "Scene Id"
+msgstr ""
+
+msgctxt "Field (costume_id)"
+msgid "Costume ID"
+msgstr ""
+
+msgctxt "Relationship (costume)"
+msgid "Costume"
+msgstr ""
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr ""
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr ""
+
+msgctxt "Custom Title ()"
+msgid "Costume Name"
+msgstr ""
+
+msgctxt "Table (scene_crew)"
+msgid "Scene Crew"
+msgstr "Szene Crew"
+
+msgctxt "Field (scene_crew_id)"
+msgid "Scene Crew ID"
+msgstr "Szene Crew ID"
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr "Kommentar"
+
+msgctxt "Field (scene_id)"
+msgid "Scene ID"
+msgstr "Szene ID"
+
+msgctxt "Field (department_id)"
+msgid "Department ID"
+msgstr ""
+
+msgctxt "Relationship (department)"
+msgid "Department"
+msgstr ""
+
+msgctxt "Relationship (scenes)"
+msgid "Scenes"
+msgstr "Szenen"
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr "bersicht"
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr "Details"
+
+msgctxt "Field (scene_equipment_id)"
+msgid "Scene Equipment ID"
+msgstr ""
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr "Kommentar"
+
+msgctxt "Field (equipment_id)"
+msgid "Equipment ID"
+msgstr ""
+
+msgctxt "Field (scene_id)"
+msgid "Scene ID"
+msgstr ""
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr ""
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr ""
+
+msgctxt "Table (scene_extras)"
+msgid "Scene Extras"
+msgstr ""
+
+msgctxt "Field (scene_extras_id)"
+msgid "Scene Extras ID"
+msgstr ""
+
+msgctxt "Field (description)"
+msgid "Description"
+msgstr ""
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr ""
+
+msgctxt "Field (scene_id)"
+msgid "Scene ID"
+msgstr ""
+
+msgctxt "Field (contact_id)"
+msgid "Contact ID"
+msgstr ""
+
+msgctxt "Relationship (contact)"
+msgid "Contact"
+msgstr ""
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr ""
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr ""
+
+msgctxt "Table (scene_makeup)"
+msgid "Scene Makeup"
+msgstr ""
+
+msgctxt "Field (scene_makeup_id)"
+msgid "Scene Makeup ID"
+msgstr ""
+
+msgctxt "Field (description)"
+msgid "Description"
+msgstr ""
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr ""
+
+msgctxt "Field (scene_id)"
+msgid "Scene ID"
+msgstr ""
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr ""
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr ""
+
+msgctxt "Table (scene_props)"
+msgid "Scene Props"
+msgstr ""
+
+msgctxt "Field (scene_props_id)"
+msgid "Scene Prop ID"
+msgstr ""
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr ""
+
+msgctxt "Field (scene_id)"
+msgid "Scene ID"
+msgstr ""
+
+msgctxt "Field (prop_id)"
+msgid "Prop ID"
+msgstr ""
+
+msgctxt "Relationship (props)"
+msgid "Props"
+msgstr ""
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr ""
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr ""
+
+msgctxt "Table (scenes)"
+msgid "Scenes"
+msgstr "TestResult1"
+
+msgctxt "Field (scene_id)"
+msgid "Scene ID"
+msgstr "Szene ID"
+
+msgctxt "Field (comments)"
+msgid "Comments"
+msgstr "Kommentar"
+
+msgctxt "Field (description)"
+msgid "Description"
+msgstr "Beschreibung"
+
+msgctxt "Field (location_id)"
+msgid "Location ID"
+msgstr ""
+
+msgctxt "Field (date)"
+msgid "Date"
+msgstr "Termin"
+
+msgctxt "Field (time)"
+msgid "Time"
+msgstr "Zeit"
+
+msgctxt "Field (minutes)"
+msgid "Stop (minutes)"
+msgstr "Stunde"
+
+msgctxt "Field (day_or_night)"
+msgid "Day/Night"
+msgstr "Tag/Nacht"
+
+msgctxt "Field Choice ()"
+msgid "Day"
+msgstr "Tag"
+
+msgctxt "Field Choice ()"
+msgid "Night"
+msgstr "Nacht"
+
+msgctxt "Field Choice ()"
+msgid "Morning"
+msgstr ""
+
+msgctxt "Field Choice ()"
+msgid "Evening"
+msgstr ""
+
+msgctxt "Field Choice ()"
+msgid "Dawn"
+msgstr ""
+
+msgctxt "Field (interior_or_exterior)"
+msgid "Interior/Exterior"
+msgstr "Interior/Exterior"
+
+msgctxt "Field Choice ()"
+msgid "Interior"
+msgstr ""
+
+msgctxt "Field Choice ()"
+msgid "Exterior"
+msgstr ""
+
+msgctxt "Field (name)"
+msgid "Name"
+msgstr "Szene"
+
+msgctxt "Field (overview_name)"
+msgid "Scene"
+msgstr ""
+
+msgctxt "Field (pages)"
+msgid "Pages"
+msgstr ""
+
+msgctxt "Field (script_day)"
+msgid "Script Day"
+msgstr ""
+
+msgctxt "Relationship (location)"
+msgid "Locations"
+msgstr ""
+
+msgctxt "Relationship (scene_crew)"
+msgid "Additional Crew"
+msgstr "Szene Crew"
+
+msgctxt "Relationship (scene_cast)"
+msgid "Cast"
+msgstr "Szene Besetzung"
+
+msgctxt "Relationship (scene_equipment)"
+msgid "Additional Equipment"
+msgstr ""
+
+msgctxt "Relationship (scene_extras)"
+msgid "Extras"
+msgstr ""
+
+msgctxt "Relationship (scene_props)"
+msgid "Props"
+msgstr ""
+
+msgctxt "Relationship (scene_costume)"
+msgid "Costume"
+msgstr ""
+
+msgctxt "Relationship (scene_makeup)"
+msgid "Additional Makeup"
+msgstr ""
+
+msgctxt "Layout Group (overview)"
+msgid "Overview"
+msgstr "bersicht"
+
+msgctxt "Layout Group (details)"
+msgid "Details"
+msgstr "Details"
+
+msgctxt "Layout Group (scenario)"
+msgid "Scenario"
+msgstr "Stimmung"
+
+msgctxt "Layout Group (location)"
+msgid "Location"
+msgstr "Location"
+
+msgctxt "Custom Title ()"
+msgid "Contact Name"
+msgstr ""
+
+msgctxt "Custom Title ()"
+msgid "Actor's Name"
+msgstr ""
diff --git a/tests/translations_po/test_document_export_po.cc b/tests/translations_po/test_document_export_po.cc
new file mode 100644
index 0000000..fd60105
--- /dev/null
+++ b/tests/translations_po/test_document_export_po.cc
@@ -0,0 +1,120 @@
+/* Glom
+ *
+ * Copyright (C) 2012 Openismus GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+71 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <libglom/document/document.h>
+#include <libglom/translations_po.h>
+#include <libglom/init.h>
+#include <libglom/utils.h>
+#include <giomm/file.h>
+#include <glibmm/convert.h>
+#include <glibmm/miscutils.h>
+#include <glibmm/fileutils.h>
+
+#include <iostream>
+
+ 
+int main()
+{
+  Glom::libglom_init();
+
+  // Get a URI for a test file:
+  Glib::ustring uri;
+
+  try
+  {
+    const std::string path =
+       Glib::build_filename(GLOM_DOCDIR_EXAMPLES_NOTINSTALLED,
+         "example_film_manager.glom");
+    uri = Glib::filename_to_uri(path);
+  }
+  catch(const Glib::ConvertError& ex)
+  {
+    std::cerr << G_STRFUNC << ": " << ex.what();
+    return EXIT_FAILURE;
+  }
+
+  //std::cout << "URI=" << uri << std::endl;
+
+
+  // Load the document:
+  Glom::Document document;
+  document.set_file_uri(uri);
+  int failure_code = 0;
+  const bool test = document.load(failure_code);
+  //std::cout << "Document load result=" << test << std::endl;
+
+  if(!test)
+  {
+    std::cerr << "Document::load() failed with failure_code=" << failure_code << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  const Glib::ustring po_file_uri = Glom::Utils::get_temp_file_uri("glom_export.po");
+  if(po_file_uri.empty())
+  {
+    std::cerr << "Could not generate a temporary file URI=" << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  //std::cout << "po file URI: " << po_file_uri << std::endl;
+
+  const Glib::ustring locale = "de_DE";
+  const bool success = 
+    Glom::write_translations_to_po_file(&document, po_file_uri, locale);
+  if(!success)
+  {
+    std::cerr << "Glom::write_translations_to_po_file() failed." << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  //Get a filepath for the URI:
+  std::string po_file_path;
+  try
+  {
+    po_file_path = Glib::filename_from_uri(po_file_uri);
+  }
+  catch(const Glib::ConvertError& ex)
+  {
+    std::cerr << G_STRFUNC << ": " << ex.what();
+    return EXIT_FAILURE;
+  }
+
+  //Check that the exported po file contains an expected string:
+  std::string data;
+  try
+  {
+    data = Glib::file_get_contents(po_file_path);
+  }
+  catch(const Glib::Error& ex)
+  {
+    std::cerr << "Failed: file_get_contents() failed: " << ex.what() << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  const bool text_found =
+    (data.find("Stabliste") != std::string::npos);
+  g_assert(text_found);
+
+  //TODO: Remove po_file_uri
+
+  Glom::libglom_deinit();
+
+  return EXIT_SUCCESS;
+}
diff --git a/tests/translations_po/test_document_import_po.cc b/tests/translations_po/test_document_import_po.cc
new file mode 100644
index 0000000..0ae3125
--- /dev/null
+++ b/tests/translations_po/test_document_import_po.cc
@@ -0,0 +1,128 @@
+/* Glom
+ *
+ * Copyright (C) 2012 Openismus GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+71 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <libglom/document/document.h>
+#include <libglom/translations_po.h>
+#include <libglom/init.h>
+#include <libglom/utils.h>
+#include <giomm/file.h>
+#include <glibmm/convert.h>
+#include <glibmm/miscutils.h>
+
+#include <iostream>
+
+ 
+int main()
+{
+  Glom::libglom_init();
+
+  // Get a URI for a test file:
+  Glib::ustring uri;
+
+  try
+  {
+    const std::string path =
+       Glib::build_filename(GLOM_DOCDIR_EXAMPLES_NOTINSTALLED,
+         "example_film_manager.glom");
+    uri = Glib::filename_to_uri(path);
+  }
+  catch(const Glib::ConvertError& ex)
+  {
+    std::cerr << G_STRFUNC << ": " << ex.what();
+    return EXIT_FAILURE;
+  }
+
+  //std::cout << "URI=" << uri << std::endl;
+
+
+  // Load the document:
+  Glom::Document document;
+  document.set_file_uri(uri);
+  int failure_code = 0;
+  const bool test = document.load(failure_code);
+  //std::cout << "Document load result=" << test << std::endl;
+
+  if(!test)
+  {
+    std::cerr << "Document::load() failed with failure_code=" << failure_code << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  document.set_allow_autosave(false); //Do not save changes back to the example file:
+  
+  Glib::ustring po_file_uri;
+  try
+  {
+    const std::string path =
+      Glib::build_filename(GLOM_TESTS_TRANSLATIONS_PO_DATA_NOTINSTALLED,
+        "test.po");
+    po_file_uri = Glib::filename_to_uri(path);
+  }
+  catch(const Glib::ConvertError& ex)
+  {
+    std::cerr << G_STRFUNC << ": " << ex.what();
+    return EXIT_FAILURE;
+  }
+
+  if(po_file_uri.empty())
+  {
+    std::cerr << "po_file_uri was empty." << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  //std::cout << "po file URI: " << po_file_uri << std::endl;
+
+  const Glib::ustring locale = "de_DE";
+  const bool success = 
+    Glom::import_translations_from_po_file(&document, po_file_uri, locale);
+  if(!success)
+  {
+    std::cerr << "Glom::import_translations_from_po_file() failed." << std::endl;
+    return EXIT_FAILURE;
+  }
+
+
+  //Check that some expected translated titles are now in the document:
+  Glom::sharedptr<const Glom::TableInfo> table = document.get_table("scenes");
+  g_assert(table);
+  g_assert( table->get_title() == "Scenes" ); //The original title should be unchanged:
+
+  //This should have a new translated title:
+  if(table->get_title_translation(locale) != "TestResult1")
+  {
+    std::cerr << "Failure: Unexpected translated report title." << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  const Glom::sharedptr<const Glom::Report> report = document.get_report("crew", "crew_list");
+  g_assert(report);
+  g_assert(report->get_title() == "Crew List"); //The original title should be unchanged:
+
+  //This should have a new translated title:
+  if(report->get_title_translation(locale) != "TestResult2")
+  {
+    std::cerr << "Failure: Unexpected translated report title." << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  Glom::libglom_deinit();
+
+  return EXIT_SUCCESS;
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]