[Libguestfs] [PATCH 3/4] Add gettext support for OCaml tools (virt-resize, virt-sparsify, virt-sysprep).

Richard W.M. Jones rjones at redhat.com
Mon Apr 30 18:00:55 UTC 2012


From: "Richard W.M. Jones" <rjones at redhat.com>

Note that this support is optional: To enable it, install the
ocaml-gettext library from
http://forge.ocamlcore.org/projects/ocaml-gettext .  If this library
is not installed, then configure detects this and inserts dummy
gettext functions that do nothing.
---
 .gitignore                                         |    3 +
 Makefile.am                                        |    8 +-
 README                                             |    2 +
 configure.ac                                       |   89 ++++++++++
 po/Makefile.am                                     |   23 ++-
 po/POTFILES-ml                                     |   36 ++++
 resize/Makefile.am                                 |    5 +
 resize/resize.ml                                   |  174 ++++++++++----------
 resize/utils.ml                                    |    6 +-
 sparsify/Makefile.am                               |    5 +
 sparsify/sparsify.ml                               |   64 +++----
 sparsify/utils.ml                                  |    4 +-
 sysprep/Makefile.am                                |    5 +
 sysprep/main.ml                                    |   79 ++++-----
 sysprep/sysprep_operation.ml                       |   18 +-
 sysprep/sysprep_operation_bash_history.ml          |    7 +-
 sysprep/sysprep_operation_cron_spool.ml            |    3 +-
 sysprep/sysprep_operation_dhcp_client_state.ml     |    3 +-
 sysprep/sysprep_operation_dhcp_server_state.ml     |    3 +-
 sysprep/sysprep_operation_dovecot_data.ml          |    3 +-
 sysprep/sysprep_operation_flag_reconfiguration.ml  |    7 +-
 sysprep/sysprep_operation_hostname.ml              |   11 +-
 sysprep/sysprep_operation_logfiles.ml              |    7 +-
 sysprep/sysprep_operation_mail_spool.ml            |    3 +-
 sysprep/sysprep_operation_net_hwaddr.ml            |    7 +-
 sysprep/sysprep_operation_package_manager_cache.ml |    3 +-
 sysprep/sysprep_operation_random_seed.ml           |    7 +-
 sysprep/sysprep_operation_rhn_systemid.ml          |    3 +-
 sysprep/sysprep_operation_samba_db_log.ml          |    3 +-
 sysprep/sysprep_operation_script.ml                |   23 +--
 sysprep/sysprep_operation_smolt_uuid.ml            |    3 +-
 sysprep/sysprep_operation_ssh_hostkeys.ml          |    7 +-
 sysprep/sysprep_operation_ssh_userdir.ml           |    7 +-
 sysprep/sysprep_operation_sssd_db_log.ml           |    3 +-
 sysprep/sysprep_operation_udev_persistent_net.ml   |    7 +-
 sysprep/sysprep_operation_user_account.ml          |   10 +-
 sysprep/sysprep_operation_utmp.ml                  |    7 +-
 sysprep/sysprep_operation_yum_uuid.ml              |    7 +-
 38 files changed, 432 insertions(+), 233 deletions(-)
 create mode 100644 po/POTFILES-ml

diff --git a/.gitignore b/.gitignore
index d420430..e5d868a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -310,6 +310,7 @@ pod2htm?.tmp
 /rescue/virt-rescue
 /rescue/virt-rescue.1
 /resize/.depend
+/resize/resize_gettext.ml
 /resize/stamp-virt-resize.pod
 /resize/utils_tests
 /resize/virt-resize
@@ -326,6 +327,7 @@ pod2htm?.tmp
 /ruby/Rakefile
 /run
 /sparsify/.depend
+/sparsify/sparsify_gettext.ml
 /sparsify/stamp-virt-sparsify.pod
 /sparsify/virt-sparsify
 /sparsify/virt-sparsify.1
@@ -354,6 +356,7 @@ pod2htm?.tmp
 /sysprep/stamp-script2.sh
 /sysprep/stamp-virt-sysprep.pod
 /sysprep/sysprep-extra-options.pod
+/sysprep/sysprep_gettext.ml
 /sysprep/sysprep-operations.pod
 /sysprep/virt-sysprep
 /sysprep/virt-sysprep.1
diff --git a/Makefile.am b/Makefile.am
index bc0a543..fde3271 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -212,8 +212,9 @@ dist-hook:
 	mv AUTHORS-t AUTHORS
 	cp AUTHORS $(distdir)/AUTHORS
 
-# Update the list of translatable files (po/POTFILES).  This has to be
-# in the top-level Makefile.am so that we have access to DIST_SUBDIRS.
+# Update the list of translatable files (po/POTFILES po/POTFILES-ml).
+# This has to be in the top-level Makefile.am so that we have access
+# to DIST_SUBDIRS.
 all-local:
 	cd $(srcdir); \
 	find $(DIST_SUBDIRS) \
@@ -231,6 +232,9 @@ all-local:
 	grep -v '^images/' | \
 	LC_ALL=C sort | \
 	sed 's,^\./,,' > po/POTFILES
+	cd $(srcdir); \
+	find resize sparsify sysprep -name '*.ml' | \
+	LC_ALL=C sort > po/POTFILES-ml
 
 # Make clean.
 
diff --git a/README b/README
index 2d913aa..5fc308c 100644
--- a/README
+++ b/README
@@ -100,6 +100,8 @@ For basic functionality and the C tools:
 - OCaml if you want to rebuild the generated files, and
   also to build the OCaml bindings (optional)
 
+- ocaml-gettext if you want to translate OCaml tools (optional)
+
 - po4a for translating manpages and POD files.
   This is optional when compiling from the tarball, but mandatory
   if you compile from git.
diff --git a/configure.ac b/configure.ac
index bcd0382..132f0e8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -776,6 +776,95 @@ AM_CONDITIONAL([HAVE_OCAML],
 AM_CONDITIONAL([HAVE_OCAMLDOC],
                [test "x$OCAMLDOC" != "xno"])
 
+OCAML_PKG_gettext=no
+AS_IF([test "x$OCAMLC" != "xno"],[
+    dnl Check for ocaml-gettext package to translate OCaml tools.
+    AC_CHECK_OCAML_PKG(gettext)
+
+    dnl Write gettext modules for each OCaml tool.  If OCaml gettext
+    dnl is not available then we write dummy functions.
+    for program in resize sparsify sysprep; do
+        output=$program/${program}_gettext.ml
+        AC_MSG_NOTICE([creating $output])
+        rm -f $output
+
+        cat <<EOF > $output
+(* This file is generated automatically by ./configure. *)
+
+(** Gettext functions for virt-$program.
+
+    The ${program}_gettext module provides gettext functions for
+    $program, or dummy functions if ocaml-gettext was not available
+    at configure time.
+
+    {b Note}: Don't translate debug strings, or strings which are
+    meant to be read/written only by machine.
+
+    There are two ways to translate constant strings in OCaml programs.
+
+    For ordinary strings, replace [["string"]] with [[s_"string"]].  Since
+    this is a function call to a function called [[s_]], you may have
+    to put parentheses around the expression.
+
+    For format strings, use:
+
+{v
+  printf (f_"zeroing filesystem %s") filename;
+v}
+
+    Note for format strings, the parentheses are almost always required,
+    and they just go around the [[(f_"string")]], {i not} around the other
+    arguments of the printf function.
+
+    At build time, a program parses the OCaml code into an abstract
+    syntax tree and statically determines all calls to the special
+    [[s_]] and [[f_]] functions, which means: (a) You can be very loose
+    with syntax, unlike ordinary xgettext, but (b) you cannot rename
+    these functions.
+*)
+
+EOF
+
+        if test "x$OCAML_PKG_gettext" != "xno"; then
+            # ocaml-gettext available: real module.
+            cat <<EOF >>$output
+module Gettext = Gettext.Program (
+  struct
+    let textdomain = "$PACKAGE_NAME"
+    let codeset = None
+    let dir = None
+    let dependencies = [[]]
+  end
+) (GettextStub.Native)
+EOF
+        else
+            # No gettext: module containing dummy gettext functions.
+            cat <<EOF >>$output
+module Gettext = struct
+  external s_ : string -> string = "%identity"
+  external f_ : ('a, 'b, 'c, 'd, 'e, 'f) format6
+    -> ('a, 'b, 'c, 'd, 'e, 'f) format6
+    = "%identity"
+  let sn_ : string -> string -> int -> string
+    = fun s p n -> if n = 1 then s else p
+  let fn_ : ('a, 'b, 'c, 'd, 'e, 'f) format6
+    -> ('a, 'b, 'c, 'd, 'e, 'f) format6
+    -> int -> ('a, 'b, 'c, 'd, 'e, 'f) format6
+    = fun s p n -> if n = 1 then s else p
+end
+EOF
+        fi
+
+        chmod -w $output
+    done
+])
+AM_CONDITIONAL([HAVE_OCAML_PKG_GETTEXT],
+        [test "x$OCAMLC" != "xno" && test "x$OCAMLFIND" != "xno" && test "x$OCAML_PKG_gettext" != "xno"])
+
+AC_CHECK_PROG([OCAML_GETTEXT],[ocaml-gettext],[ocaml-gettext],[no])
+AM_CONDITIONAL([HAVE_OCAML_GETTEXT],
+        [test "x$OCAMLC" != "xno" && test "x$OCAMLFIND" != "xno" && test "x$OCAML_PKG_gettext" != "xno" && test "x$OCAML_GETTEXT" != "xno"])
+
 dnl Check for Perl (optional, for Perl bindings and Perl tools).
 PERL=no
 AC_ARG_ENABLE([perl],
diff --git a/po/Makefile.am b/po/Makefile.am
index c6f1acd..8c3d42c 100644
--- a/po/Makefile.am
+++ b/po/Makefile.am
@@ -25,12 +25,13 @@ MSGID_BUGS_ADDRESS = https://bugzilla.redhat.com/enter_bug.cgi?component=libgues
 # Don't use LINGUAS (uppercase) as Gentoo defines it (RHBZ#804464).
 linguas     := en_GB es gu hi kn ml mr nl or pa pl ta te uk
 
-POTFILES := $(shell $(SED) 's,^,$(top_srcdir)/,' POTFILES)
-POFILES  := $(linguas:%=%.po)
-GMOFILES := $(linguas:%=%.gmo)
+POTFILES    := $(shell $(SED) 's,^,$(top_srcdir)/,' POTFILES)
+POTFILES_ML := $(shell $(SED) 's,^,$(top_srcdir)/,' POTFILES-ml)
+POFILES     := $(linguas:%=%.po)
+GMOFILES    := $(linguas:%=%.gmo)
 
 EXTRA_DIST = \
-	POTFILES \
+	POTFILES POTFILES-ml \
 	$(DOMAIN).pot \
 	$(POFILES) \
 	$(GMOFILES)
@@ -48,8 +49,8 @@ update-po:
 
 update-gmo: Makefile $(GMOFILES)
 
-$(DOMAIN).pot: Makefile $(POTFILES)
-	rm -f $@ $@-t
+$(DOMAIN).pot: Makefile $(POTFILES) $(POTFILES-ml)
+	rm -f $@-t $@-ml
 	$(XGETTEXT) \
 	  -o $@-t \
 	  --default-domain=$(DOMAIN) \
@@ -66,6 +67,16 @@ $(DOMAIN).pot: Makefile $(POTFILES)
 	  --msgid-bugs-address="$(MSGID_BUGS_ADDRESS)" \
 	  --directory=$(top_srcdir) \
 	  --files-from=$(abs_srcdir)/POTFILES
+if HAVE_OCAML_GETTEXT
+	cd $(top_srcdir) && \
+	$(OCAML_GETTEXT) --action extract --extract-pot po/$@-ml \
+	  $$(cat $(abs_srcdir)/POTFILES-ml)
+# Don't trust msgcat since it will definitely screw up.  Instead, chop
+# the head from the second file and append it to the first.
+	echo >> $@-t
+	awk '/^#:/{i++}i{print}' < $@-ml >> $@-t
+	rm $@-ml
+endif
 	mv $@-t $@
 
 %.po: $(DOMAIN).pot
diff --git a/po/POTFILES-ml b/po/POTFILES-ml
new file mode 100644
index 0000000..b08aff9
--- /dev/null
+++ b/po/POTFILES-ml
@@ -0,0 +1,36 @@
+resize/progress.ml
+resize/resize.ml
+resize/resize_gettext.ml
+resize/utils.ml
+resize/utils_tests.ml
+sparsify/progress.ml
+sparsify/sparsify.ml
+sparsify/sparsify_gettext.ml
+sparsify/utils.ml
+sysprep/main.ml
+sysprep/sysprep_gettext.ml
+sysprep/sysprep_operation.ml
+sysprep/sysprep_operation_bash_history.ml
+sysprep/sysprep_operation_cron_spool.ml
+sysprep/sysprep_operation_dhcp_client_state.ml
+sysprep/sysprep_operation_dhcp_server_state.ml
+sysprep/sysprep_operation_dovecot_data.ml
+sysprep/sysprep_operation_flag_reconfiguration.ml
+sysprep/sysprep_operation_hostname.ml
+sysprep/sysprep_operation_logfiles.ml
+sysprep/sysprep_operation_mail_spool.ml
+sysprep/sysprep_operation_net_hwaddr.ml
+sysprep/sysprep_operation_package_manager_cache.ml
+sysprep/sysprep_operation_random_seed.ml
+sysprep/sysprep_operation_rhn_systemid.ml
+sysprep/sysprep_operation_samba_db_log.ml
+sysprep/sysprep_operation_script.ml
+sysprep/sysprep_operation_smolt_uuid.ml
+sysprep/sysprep_operation_ssh_hostkeys.ml
+sysprep/sysprep_operation_ssh_userdir.ml
+sysprep/sysprep_operation_sssd_db_log.ml
+sysprep/sysprep_operation_udev_persistent_net.ml
+sysprep/sysprep_operation_user_account.ml
+sysprep/sysprep_operation_utmp.ml
+sysprep/sysprep_operation_yum_uuid.ml
+sysprep/utils.ml
diff --git a/resize/Makefile.am b/resize/Makefile.am
index bd15a32..b8ae6af 100644
--- a/resize/Makefile.am
+++ b/resize/Makefile.am
@@ -30,6 +30,7 @@ SOURCES = \
 	progress.mli \
 	progress.ml \
 	resize.ml \
+	resize_gettext.ml \
 	utils.ml \
 	utils_tests.ml
 
@@ -39,6 +40,7 @@ if HAVE_OCAML
 OBJECTS = \
 	$(top_builddir)/fish/guestfish-progress.o \
 	progress_c.o \
+	resize_gettext.cmx \
 	utils.cmx \
 	progress.cmx \
 	resize.cmx
@@ -49,6 +51,9 @@ bin_SCRIPTS = virt-resize
 # option to be passed to gcc, so we don't try linking against an
 # installed copy of libguestfs.
 OCAMLPACKAGES = -package str -I $(top_builddir)/src/.libs -I ../ocaml
+if HAVE_OCAML_PKG_GETTEXT
+OCAMLPACKAGES += -package gettext-stub
+endif
 
 OCAMLCFLAGS = -g -warn-error CDEFLMPSUVYZX $(OCAMLPACKAGES)
 OCAMLOPTFLAGS = $(OCAMLCFLAGS)
diff --git a/resize/resize.ml b/resize/resize.ml
index 95bc774..256148d 100644
--- a/resize/resize.ml
+++ b/resize/resize.ml
@@ -18,6 +18,8 @@
 
 open Printf
 
+open Resize_gettext.Gettext
+
 module G = Guestfs
 
 open Utils
@@ -38,7 +40,7 @@ let infile, outfile, align_first, alignment, copy_boot_loader,
   let display_version () =
     let g = new G.guestfs () in
     let version = g#version () in
-    printf "virt-resize %Ld.%Ld.%Ld%s\n"
+    printf (f_"virt-resize %Ld.%Ld.%Ld%s\n")
       version.G.major version.G.minor version.G.release version.G.extra;
     exit 0
   in
@@ -54,8 +56,8 @@ let infile, outfile, align_first, alignment, copy_boot_loader,
   let dryrun = ref false in
   let expand = ref "" in
   let set_expand s =
-    if s = "" then error "%s: empty --expand option" prog
-    else if !expand <> "" then error "--expand option given twice"
+    if s = "" then error (f_"%s: empty --expand option") prog
+    else if !expand <> "" then error (f_"--expand option given twice")
     else expand := s
   in
   let expand_content = ref true in
@@ -71,51 +73,51 @@ let infile, outfile, align_first, alignment, copy_boot_loader,
   let resizes_force = ref [] in
   let shrink = ref "" in
   let set_shrink s =
-    if s = "" then error "empty --shrink option"
-    else if !shrink <> "" then error "--shrink option given twice"
+    if s = "" then error (f_"empty --shrink option")
+    else if !shrink <> "" then error (f_"--shrink option given twice")
     else shrink := s
   in
 
   let argspec = Arg.align [
-    "--align-first", Arg.Set_string align_first, "never|always|auto Align first partition (default: auto)";
-    "--alignment", Arg.Set_int alignment,   "sectors Set partition alignment (default: 128 sectors)";
-    "--no-copy-boot-loader", Arg.Clear copy_boot_loader, " Don't copy boot loader";
-    "-d",        Arg.Set debug,             " Enable debugging messages";
+    "--align-first", Arg.Set_string align_first, s_"never|always|auto" ^ " " ^ s_"Align first partition (default: auto)";
+    "--alignment", Arg.Set_int alignment,   s_"sectors" ^ " " ^ s_"Set partition alignment (default: 128 sectors)";
+    "--no-copy-boot-loader", Arg.Clear copy_boot_loader, " " ^ s_"Don't copy boot loader";
+    "-d",        Arg.Set debug,             " " ^ s_"Enable debugging messages";
     "--debug",   Arg.Set debug,             " -\"-";
-    "--debug-gc",Arg.Set debug_gc,          " Debug GC and memory allocations";
-    "--delete",  Arg.String (add deletes),  "part Delete partition";
-    "--expand",  Arg.String set_expand,     "part Expand partition";
-    "--no-expand-content", Arg.Clear expand_content, " Don't expand content";
-    "--no-extra-partition", Arg.Clear extra_partition, " Don't create extra partition";
-    "--format",  Arg.Set_string format,     "format Format of input disk";
-    "--ignore",  Arg.String (add ignores),  "part Ignore partition";
-    "--lv-expand", Arg.String (add lv_expands), "lv Expand logical volume";
-    "--LV-expand", Arg.String (add lv_expands), "lv -\"-";
-    "--lvexpand", Arg.String (add lv_expands), "lv -\"-";
-    "--LVexpand", Arg.String (add lv_expands), "lv -\"-";
-    "--machine-readable", Arg.Set machine_readable, " Make output machine readable";
-    "-n",        Arg.Set dryrun,            " Don't perform changes";
+    "--debug-gc",Arg.Set debug_gc,          " " ^ s_"Debug GC and memory allocations";
+    "--delete",  Arg.String (add deletes),  s_"part" ^ " " ^ s_"Delete partition";
+    "--expand",  Arg.String set_expand,     s_"part" ^ " " ^ s_"Expand partition";
+    "--no-expand-content", Arg.Clear expand_content, " " ^ s_"Don't expand content";
+    "--no-extra-partition", Arg.Clear extra_partition, " " ^ s_"Don't create extra partition";
+    "--format",  Arg.Set_string format,     s_"format" ^ " " ^ s_"Format of input disk";
+    "--ignore",  Arg.String (add ignores),  s_"part" ^ " " ^ s_"Ignore partition";
+    "--lv-expand", Arg.String (add lv_expands), s_"lv" ^ " " ^ s_"Expand logical volume";
+    "--LV-expand", Arg.String (add lv_expands), s_"lv" ^ " -\"-";
+    "--lvexpand", Arg.String (add lv_expands), s_"lv" ^ " -\"-";
+    "--LVexpand", Arg.String (add lv_expands), s_"lv" ^ " -\"-";
+    "--machine-readable", Arg.Set machine_readable, " " ^ s_"Make output machine readable";
+    "-n",        Arg.Set dryrun,            " " ^ s_"Don't perform changes";
     "--dryrun",  Arg.Set dryrun,            " -\"-";
     "--dry-run", Arg.Set dryrun,            " -\"-";
-    "--ntfsresize-force", Arg.Set ntfsresize_force, " Force ntfsresize";
-    "--output-format", Arg.Set_string output_format, "format Format of output disk";
-    "-q",        Arg.Set quiet,             " Don't print the summary";
+    "--ntfsresize-force", Arg.Set ntfsresize_force, " " ^ s_"Force ntfsresize";
+    "--output-format", Arg.Set_string output_format, s_"format" ^ " " ^ s_"Format of output disk";
+    "-q",        Arg.Set quiet,             " " ^ s_"Don't print the summary";
     "--quiet",   Arg.Set quiet,             " -\"-";
-    "--resize",  Arg.String (add resizes),  "part=size Resize partition";
-    "--resize-force", Arg.String (add resizes_force), "part=size Forcefully resize partition";
-    "--shrink",  Arg.String set_shrink,     "part Shrink partition";
-    "-V",        Arg.Unit display_version,  " Display version and exit";
+    "--resize",  Arg.String (add resizes),  s_"part=size" ^ " " ^ s_"Resize partition";
+    "--resize-force", Arg.String (add resizes_force), s_"part=size" ^ " " ^ s_"Forcefully resize partition";
+    "--shrink",  Arg.String set_shrink,     s_"part" ^ " " ^ s_"Shrink partition";
+    "-V",        Arg.Unit display_version,  " " ^ s_"Display version and exit";
     "--version", Arg.Unit display_version,  " -\"-";
   ] in
   let disks = ref [] in
   let anon_fun s = disks := s :: !disks in
   let usage_msg =
-    sprintf "\
+    sprintf (f_"\
 %s: resize a virtual machine disk
 
 A short summary of the options is given below.  For detailed help please
 read the man page virt-resize(1).
-"
+")
       prog in
   Arg.parse argspec anon_fun usage_msg;
 
@@ -147,7 +149,7 @@ read the man page virt-resize(1).
   let shrink = match !shrink with "" -> None | str -> Some str in
 
   if alignment < 1 then
-    error "alignment cannot be < 1";
+    error (f_"alignment cannot be < 1");
   let alignment = Int64.of_int alignment in
 
   let align_first =
@@ -156,7 +158,7 @@ read the man page virt-resize(1).
     | "always" -> `Always
     | "auto" -> `Auto
     | _ ->
-      error "unknown --align-first option: use never|always|auto" in
+      error (f_"unknown --align-first option: use never|always|auto") in
 
   (* No arguments and machine-readable mode?  Print out some facts
    * about what this binary supports.  We only need to print out new
@@ -185,13 +187,13 @@ read the man page virt-resize(1).
     match List.rev !disks with
     | [infile; outfile] -> infile, outfile
     | _ ->
-        error "usage is: %s [--options] indisk outdisk" prog in
+        error (f_"usage is: %s [--options] indisk outdisk") prog in
 
   (* Simple-minded check that the user isn't trying to use the
    * same disk for input and output.
    *)
   if infile = outfile then
-    error "you cannot use the same disk image for input and output";
+    error (f_"you cannot use the same disk image for input and output");
 
   infile, outfile, align_first, alignment, copy_boot_loader,
   debug, debug_gc, deletes,
@@ -225,7 +227,7 @@ let connect_both_disks () =
 
 let g =
   if not quiet then
-    printf "Examining %s ...\n%!" infile;
+    printf (f_"Examining %s ...\n%!") infile;
 
   let g = connect_both_disks () in
 
@@ -263,10 +265,10 @@ let max_bootloader =
 (* Check the disks are at least as big as the bootloader. *)
 let () =
   if insize < Int64.of_int max_bootloader then
-    error "%s: file is too small to be a disk image (%Ld bytes)"
+    error (f_"%s: file is too small to be a disk image (%Ld bytes)")
       infile insize;
   if outsize < Int64.of_int max_bootloader then
-    error "%s: file is too small to be a disk image (%Ld bytes)"
+    error (f_"%s: file is too small to be a disk image (%Ld bytes)")
       outfile outsize
 
 (* Get the source partition type. *)
@@ -280,7 +282,7 @@ let parttype, parttype_string =
   | "msdos" -> MBR, "msdos"
   | "gpt" -> GPT, "gpt"
   | _ ->
-    error "%s: unknown partition table type\nvirt-resize only supports MBR (DOS) and GPT partition tables." infile
+    error (f_"%s: unknown partition table type\nvirt-resize only supports MBR (DOS) and GPT partition tables.") infile
 
 (* Build a data structure describing the source disk's partition layout.
  *
@@ -330,7 +332,7 @@ and string_of_partition_content = function
   | ContentExtendedPartition -> "extended partition"
 and string_of_partition_content_no_size = function
   | ContentUnknown -> "unknown data"
-  | ContentPV _ -> sprintf "LVM PV"
+  | ContentPV _ -> "LVM PV"
   | ContentFS (fs, _) -> sprintf "filesystem %s" fs
   | ContentExtendedPartition -> "extended partition"
 
@@ -344,7 +346,7 @@ let get_partition_content =
       else if fs = "LVM2_member" then (
         let rec loop = function
           | [] ->
-              error "%s: physical volume not returned by pvs_full"
+              error (f_"%s: physical volume not returned by pvs_full")
                 dev
           | pv :: _ when canonicalize pv.G.pv_name = dev ->
               ContentPV pv.G.pv_size
@@ -369,7 +371,7 @@ let partitions : partition list =
   let parts = Array.to_list (g#part_list "/dev/sda") in
 
   if List.length parts = 0 then
-    error "the source disk has no partitions";
+    error (f_"the source disk has no partitions");
 
   (* Filter out logical partitions.  See note above. *)
   let parts =
@@ -414,12 +416,12 @@ let partitions : partition list =
     | { p_name = name; p_part = { G.part_size = size };
         p_type = ContentPV pv_size }
         when size < pv_size ->
-        error "%s: partition size %Ld < physical volume size %Ld"
+        error (f_"%s: partition size %Ld < physical volume size %Ld")
           name size pv_size
     | { p_name = name; p_part = { G.part_size = size };
         p_type = ContentFS (_, fs_size) }
         when size < fs_size ->
-        error "%s: partition size %Ld < filesystem size %Ld"
+        error (f_"%s: partition size %Ld < filesystem size %Ld")
           name size fs_size
     | _ -> ()
   ) partitions;
@@ -429,7 +431,7 @@ let partitions : partition list =
     | [] -> ()
     | { p_name = name; p_part = { G.part_start = part_start } } :: _
         when end_of_prev > part_start ->
-        error "%s: this partition overlaps the previous one" name
+        error (f_"%s: this partition overlaps the previous one") name
     | { p_part = { G.part_end = part_end } } :: parts -> loop part_end parts
   in
   loop 0L partitions;
@@ -482,10 +484,10 @@ type expand_content_method =
   | PVResize | Resize2fs | NTFSResize | BtrfsFilesystemResize
 
 let string_of_expand_content_method = function
-  | PVResize -> "pvresize"
-  | Resize2fs -> "resize2fs"
-  | NTFSResize -> "ntfsresize"
-  | BtrfsFilesystemResize -> "btrfs-filesystem-resize"
+  | PVResize -> s_"pvresize"
+  | Resize2fs -> s_"resize2fs"
+  | NTFSResize -> s_"ntfsresize"
+  | BtrfsFilesystemResize -> s_"btrfs-filesystem-resize"
 
 let can_expand_content =
   if expand_content then
@@ -532,15 +534,15 @@ let find_partition =
     let partition =
       try Hashtbl.find hash name
       with Not_found ->
-        error "%s: partition not found in the source disk image (this error came from '%s' option on the command line).  Try running this command: virt-filesystems --partitions --long -a %s"
+        error (f_"%s: partition not found in the source disk image (this error came from '%s' option on the command line).  Try running this command: virt-filesystems --partitions --long -a %s")
           name option infile in
 
     if partition.p_operation = OpIgnore then
-      error "%s: partition already ignored, you cannot use it in '%s' option"
+      error (f_"%s: partition already ignored, you cannot use it in '%s' option")
         name option;
 
     if partition.p_operation = OpDelete then
-      error "%s: partition already deleted, you cannot use it in '%s' option"
+      error (f_"%s: partition already deleted, you cannot use it in '%s' option")
         name option;
 
     partition
@@ -572,11 +574,11 @@ let mark_partition_for_resize ~option ?(force = false) p newsize =
 
   (match p.p_operation with
    | OpResize _ ->
-       error "%s: this partition has already been marked for resizing"
+       error (f_"%s: this partition has already been marked for resizing")
          name
    | OpIgnore | OpDelete ->
        (* This error should have been caught already by find_partition ... *)
-       error "%s: this partition has already been ignored or deleted"
+       error (f_"%s: this partition has already been ignored or deleted")
          name
    | OpCopy -> ()
   );
@@ -591,18 +593,18 @@ let mark_partition_for_resize ~option ?(force = false) p newsize =
        *)
       match p.p_type with
       | ContentUnknown ->
-          error "%s: This partition has unknown content which might be damaged by shrinking it.  If you want to shrink this partition, you need to use the '--resize-force' option, but that could destroy any data on this partition.  (This error came from '%s' option on the command line.)"
+          error (f_"%s: This partition has unknown content which might be damaged by shrinking it.  If you want to shrink this partition, you need to use the '--resize-force' option, but that could destroy any data on this partition.  (This error came from '%s' option on the command line.)")
             name option
       | ContentPV size when size > newsize ->
-          error "%s: This partition has contains an LVM physical volume which will be damaged by shrinking it below %Ld bytes (user asked to shrink it to %Ld bytes).  If you want to shrink this partition, you need to use the '--resize-force' option, but that could destroy any data on this partition.  (This error came from '%s' option on the command line.)"
+          error (f_"%s: This partition has contains an LVM physical volume which will be damaged by shrinking it below %Ld bytes (user asked to shrink it to %Ld bytes).  If you want to shrink this partition, you need to use the '--resize-force' option, but that could destroy any data on this partition.  (This error came from '%s' option on the command line.)")
             name size newsize option
       | ContentPV _ -> ()
       | ContentFS (fstype, size) when size > newsize ->
-          error "%s: This partition has contains a %s filesystem which will be damaged by shrinking it below %Ld bytes (user asked to shrink it to %Ld bytes).  If you want to shrink this partition, you need to use the '--resize-force' option, but that could destroy any data on this partition.  (This error came from '%s' option on the command line.)"
+          error (f_"%s: This partition has contains a %s filesystem which will be damaged by shrinking it below %Ld bytes (user asked to shrink it to %Ld bytes).  If you want to shrink this partition, you need to use the '--resize-force' option, but that could destroy any data on this partition.  (This error came from '%s' option on the command line.)")
             name fstype size newsize option
       | ContentFS _ -> ()
       | ContentExtendedPartition ->
-          error "%s: This extended partition contains logical partitions which might be damaged by shrinking it.  If you want to shrink this partition, you need to use the '--resize-force' option, but that could destroy logical partitions within this partition.  (This error came from '%s' option on the command line.)"
+          error (f_"%s: This extended partition contains logical partitions which might be damaged by shrinking it.  If you want to shrink this partition, you need to use the '--resize-force' option, but that could destroy logical partitions within this partition.  (This error came from '%s' option on the command line.)")
             name option
     );
 
@@ -620,7 +622,7 @@ let () =
         if n == 0 then raise Not_found;
         String.sub arg 0 i, String.sub arg (i+1) n
       with Not_found ->
-        error "%s: missing size field in '%s' option" arg option in
+        error (f_"%s: missing size field in '%s' option") arg option in
 
     let p = find_partition ~option dev in
 
@@ -629,7 +631,7 @@ let () =
     let newsize = parse_size oldsize sizefield in
 
     if newsize <= 0L then
-      error "%s: new partition size is zero or negative" dev;
+      error (f_"%s: new partition size is zero or negative") dev;
 
     mark_partition_for_resize ~option ~force p newsize
   in
@@ -668,7 +670,7 @@ let calculate_surplus () =
 (* Handle --expand and --shrink options. *)
 let () =
   if expand <> None && shrink <> None then
-    error "you cannot use options --expand and --shrink together";
+    error (f_"you cannot use options --expand and --shrink together");
 
   if expand <> None || shrink <> None then (
     let surplus = calculate_surplus () in
@@ -680,7 +682,7 @@ let () =
      | None -> ()
      | Some dev ->
          if surplus < 0L then
-           error "You cannot use --expand when there is no surplus space to expand into.  You need to make the target disk larger by at least %s."
+           error (f_"You cannot use --expand when there is no surplus space to expand into.  You need to make the target disk larger by at least %s.")
              (human_size (Int64.neg surplus));
 
          let option = "--expand" in
@@ -692,7 +694,7 @@ let () =
      | None -> ()
      | Some dev ->
          if surplus > 0L then
-           error "You cannot use --shrink when there is no deficit (see 'deficit' in the virt-resize(1) man page).";
+           error (f_"You cannot use --shrink when there is no deficit (see 'deficit' in the virt-resize(1) man page).");
 
          let option = "--shrink" in
          let p = find_partition ~option dev in
@@ -709,7 +711,7 @@ let surplus =
 
   if surplus < 0L then (
     let deficit = Int64.neg surplus in
-    error "There is a deficit of %Ld bytes (%s).  You need to make the target disk larger by at least this amount or adjust your resizing requests."
+    error (f_"There is a deficit of %Ld bytes (%s).  You need to make the target disk larger by at least this amount or adjust your resizing requests.")
       deficit (human_size deficit)
   );
 
@@ -725,7 +727,7 @@ let () =
       let lv =
         try Hashtbl.find hash name
         with Not_found ->
-          error "%s: logical volume not found in the source disk image (this error came from '--lv-expand' option on the command line).  Try running this command: virt-filesystems --logical-volumes --long -a %s"
+          error (f_"%s: logical volume not found in the source disk image (this error came from '--lv-expand' option on the command line).  Try running this command: virt-filesystems --logical-volumes --long -a %s")
             name infile in
       lv.lv_operation <- LVOpExpand
   ) lv_expands
@@ -743,16 +745,16 @@ let () =
         let text =
           match p.p_operation with
           | OpCopy ->
-              sprintf "%s: This partition will be left alone." name
+              sprintf (f_"%s: This partition will be left alone.") name
           | OpIgnore ->
-              sprintf "%s: This partition will be created, but the contents will be ignored (ie. not copied to the target)." name
+              sprintf (f_"%s: This partition will be created, but the contents will be ignored (ie. not copied to the target).") name
           | OpDelete ->
-              sprintf "%s: This partition will be deleted." name
+              sprintf (f_"%s: This partition will be deleted.") name
           | OpResize newsize ->
-              sprintf "%s: This partition will be resized from %s to %s."
+              sprintf (f_"%s: This partition will be resized from %s to %s.")
                 name (human_size oldsize) (human_size newsize) ^
               if can_expand_content p.p_type then (
-                sprintf "  The %s on %s will be expanded using the '%s' method."
+                sprintf (f_"  The %s on %s will be expanded using the '%s' method.")
                   (string_of_partition_content_no_size p.p_type)
                   name
                   (string_of_expand_content_method
@@ -768,10 +770,10 @@ let () =
         | LVOpNone -> ()
         | LVOpExpand ->
             let text =
-              sprintf "%s: This logical volume will be expanded to maximum size."
+              sprintf (f_"%s: This logical volume will be expanded to maximum size.")
                 name ^
               if can_expand_content lv.lv_type then (
-                sprintf "  The %s on %s will be expanded using the '%s' method."
+                sprintf (f_"  The %s on %s will be expanded using the '%s' method.")
                   (string_of_partition_content_no_size lv.lv_type)
                   name
                   (string_of_expand_content_method
@@ -783,14 +785,14 @@ let () =
 
     if surplus > 0L then (
       let text =
-        sprintf "There is a surplus of %s." (human_size surplus) ^
+        sprintf (f_"There is a surplus of %s.") (human_size surplus) ^
         if extra_partition then (
           if surplus >= min_extra_partition then
-            sprintf "  An extra partition will be created for the surplus."
+            s_"  An extra partition will be created for the surplus."
           else
-            sprintf "  The surplus space is not large enough for an extra partition to be created and so it will just be ignored."
+            s_"  The surplus space is not large enough for an extra partition to be created and so it will just be ignored."
         ) else
-          sprintf "  The surplus space will be ignored.  Run a partitioning program in the guest to partition this extra space if you want." in
+          s_"  The surplus space will be ignored.  Run a partitioning program in the guest to partition this extra space if you want." in
 
       wrap (text ^ "\n\n")
     );
@@ -824,7 +826,7 @@ let g =
    * relaunching another handle.
    *)
   if not quiet then
-    printf "Setting up initial partition table on %s ...\n%!" outfile;
+    printf (f_"Setting up initial partition table on %s ...\n%!") outfile;
 
   let last_error = ref "" in
   let rec initialize_partition_table g attempts =
@@ -845,7 +847,7 @@ let g =
 
   let g, ok = initialize_partition_table g 5 in
   if not ok then
-    error "Failed to initialize the partition table on the target disk.  You need to wipe or recreate the target disk and then run virt-resize again.\n\nThe underlying error was: %s" !last_error;
+    error (f_"Failed to initialize the partition table on the target disk.  You need to wipe or recreate the target disk and then run virt-resize again.\n\nThe underlying error was: %s") !last_error;
 
   g
 
@@ -858,7 +860,7 @@ let () =
   if copy_boot_loader then (
     let bootsect = g#pread_device "/dev/sda" 446 0L in
     if String.length bootsect < 446 then
-      error "pread-device: short read";
+      error (f_"pread-device: short read");
     ignore (g#pwrite_device "/dev/sdb" bootsect 0L);
 
     let start =
@@ -871,7 +873,7 @@ let () =
 
     let loader = g#pread_device "/dev/sda" max_bootloader start in
     if String.length loader < max_bootloader then
-      error "pread-device: short read";
+      error (f_"pread-device: short read");
     ignore (g#pwrite_device "/dev/sdb" loader start)
   )
 
@@ -1016,7 +1018,7 @@ let () =
         let target = sprintf "/dev/sdb%d" p.p_target_partnum in
 
         if not quiet then
-          printf "Copying %s ...\n%!" source;
+          printf (f_"Copying %s ...\n%!") source;
 
         (match p.p_type with
          | ContentUnknown | ContentPV _ | ContentFS _ ->
@@ -1069,10 +1071,10 @@ let () =
       (* Sanity check: it contains the NTFS magic. *)
       let magic = g#pread_device target 8 3L in
       if magic <> "NTFS    " then
-        eprintf "warning: first partition is NTFS but does not contain NTFS boot loader magic\n%!"
+        eprintf (f_"warning: first partition is NTFS but does not contain NTFS boot loader magic\n%!")
       else (
         if not quiet then
-          printf "Fixing first NTFS partition boot record ...\n%!";
+          printf (f_"Fixing first NTFS partition boot record ...\n%!");
 
         if debug then (
           let old_hidden = int_of_le32 (g#pread_device target 4 0x1c_L) in
@@ -1147,7 +1149,7 @@ let () =
           let meth = expand_content_method p.p_type in
 
           if not quiet then
-            printf "Expanding %s%s using the '%s' method ...\n%!"
+            printf (f_"Expanding %s%s using the '%s' method ...\n%!")
               source
               (if source <> target then sprintf " (now %s)" target else "")
               (string_of_expand_content_method meth);
@@ -1164,7 +1166,7 @@ let () =
           let meth = expand_content_method lv.lv_type in
 
           if not quiet then
-            printf "Expanding %s using the '%s' method ...\n%!"
+            printf (f_"Expanding %s using the '%s' method ...\n%!")
               name
               (string_of_expand_content_method meth);
 
@@ -1185,7 +1187,7 @@ let () =
 
   if not quiet then (
     print_newline ();
-    wrap "Resize operation completed with no errors.  Before deleting the old disk, carefully check that the resized disk boots and works correctly.\n";
+    wrap (s_"Resize operation completed with no errors.  Before deleting the old disk, carefully check that the resized disk boots and works correctly.\n");
   );
 
   if debug_gc then
diff --git a/resize/utils.ml b/resize/utils.ml
index 3c253da..d99f489 100644
--- a/resize/utils.ml
+++ b/resize/utils.ml
@@ -18,6 +18,8 @@
 
 open Printf
 
+open Resize_gettext.Gettext
+
 module G = Guestfs
 
 let ( +^ ) = Int64.add
@@ -72,11 +74,11 @@ let wrap ?(chan = stdout) ?(hanging = 0) str =
 
 let error fs =
   let display str =
-    wrap ~chan:stderr ("virt-resize: error: " ^ str);
+    wrap ~chan:stderr (s_"virt-resize: error: " ^ str);
     prerr_newline ();
     prerr_newline ();
     wrap ~chan:stderr
-      "If reporting bugs, run virt-resize with the '-d' option and include the complete output.";
+      (s_"If reporting bugs, run virt-resize with the '-d' option and include the complete output.");
     prerr_newline ();
     exit 1
   in
diff --git a/sparsify/Makefile.am b/sparsify/Makefile.am
index efe8278..7de66b7 100644
--- a/sparsify/Makefile.am
+++ b/sparsify/Makefile.am
@@ -30,6 +30,7 @@ SOURCES = \
 	progress.mli \
 	progress.ml \
 	sparsify.ml \
+	sparsify_gettext.ml \
 	utils.ml
 
 if HAVE_OCAML
@@ -38,6 +39,7 @@ if HAVE_OCAML
 OBJECTS = \
 	$(top_builddir)/fish/guestfish-progress.o \
 	progress_c.o \
+	sparsify_gettext.cmx \
 	utils.cmx \
 	progress.cmx \
 	sparsify.cmx
@@ -48,6 +50,9 @@ bin_SCRIPTS = virt-sparsify
 # option to be passed to gcc, so we don't try linking against an
 # installed copy of libguestfs.
 OCAMLPACKAGES = -package unix -I $(top_builddir)/src/.libs -I $(top_builddir)/ocaml
+if HAVE_OCAML_PKG_GETTEXT
+OCAMLPACKAGES += -package gettext-stub
+endif
 
 OCAMLCFLAGS = -g -warn-error CDEFLMPSUVYZX $(OCAMLPACKAGES)
 OCAMLOPTFLAGS = $(OCAMLCFLAGS)
diff --git a/sparsify/sparsify.ml b/sparsify/sparsify.ml
index a1382e6..600a9e5 100644
--- a/sparsify/sparsify.ml
+++ b/sparsify/sparsify.ml
@@ -19,6 +19,8 @@
 open Unix
 open Printf
 
+open Sparsify_gettext.Gettext
+
 module G = Guestfs
 
 open Utils
@@ -54,33 +56,33 @@ let indisk, outdisk, compress, convert, debug_gc,
   let zeroes = ref [] in
 
   let argspec = Arg.align [
-    "--compress", Arg.Set compress,         " Compressed output format";
-    "--convert", Arg.Set_string convert,    "format Format of output disk (default: same as input)";
-    "--debug-gc", Arg.Set debug_gc,         " Debug GC and memory allocations";
-    "--format",  Arg.Set_string format,     "format Format of input disk";
-    "--ignore",  Arg.String (add ignores),  "fs Ignore filesystem";
-    "--machine-readable", Arg.Set machine_readable, " Make output machine readable";
-    "-o",        Arg.Set_string option,     "option Add qemu-img options";
-    "-q",        Arg.Set quiet,             " Quiet output";
+    "--compress", Arg.Set compress,         " " ^ s_"Compressed output format";
+    "--convert", Arg.Set_string convert,    s_"format" ^ " " ^ s_"Format of output disk (default: same as input)";
+    "--debug-gc", Arg.Set debug_gc,         " " ^ s_"Debug GC and memory allocations";
+    "--format",  Arg.Set_string format,     s_"format" ^ " " ^ s_"Format of input disk";
+    "--ignore",  Arg.String (add ignores),  s_"fs" ^ " " ^ s_"Ignore filesystem";
+    "--machine-readable", Arg.Set machine_readable, " " ^ s_"Make output machine readable";
+    "-o",        Arg.Set_string option,     s_"option" ^ " " ^ s_"Add qemu-img options";
+    "-q",        Arg.Set quiet,             " " ^ s_"Quiet output";
     "--quiet",   Arg.Set quiet,             " -\"-";
-    "-v",        Arg.Set verbose,           " Enable debugging messages";
+    "-v",        Arg.Set verbose,           " " ^ s_"Enable debugging messages";
     "--verbose", Arg.Set verbose,           " -\"-";
-    "-V",        Arg.Unit display_version,  " Display version and exit";
+    "-V",        Arg.Unit display_version,  " " ^ s_"Display version and exit";
     "--version", Arg.Unit display_version,  " -\"-";
-    "-x",        Arg.Set trace,             " Enable tracing of libguestfs calls";
-    "--zero",    Arg.String (add zeroes),   "fs Zero filesystem";
+    "-x",        Arg.Set trace,             " " ^ s_"Enable tracing of libguestfs calls";
+    "--zero",    Arg.String (add zeroes),   s_"fs" ^ " " ^ s_"Zero filesystem";
   ] in
   let disks = ref [] in
   let anon_fun s = disks := s :: !disks in
   let usage_msg =
-    sprintf "\
+    sprintf (f_"\
 %s: sparsify a virtual machine disk
 
  virt-sparsify [--options] indisk outdisk
 
 A short summary of the options is given below.  For detailed help please
 read the man page virt-sparsify(1).
-"
+")
       prog in
   Arg.parse argspec anon_fun usage_msg;
 
@@ -125,7 +127,7 @@ read the man page virt-sparsify(1).
    * same disk for input and output.
    *)
   if indisk = outdisk then
-    error "you cannot use the same disk image for input and output";
+    error (f_"you cannot use the same disk image for input and output");
 
   (* The input disk must be an absolute path, so we can store the name
    * in the overlay disk.
@@ -141,10 +143,10 @@ read the man page virt-sparsify(1).
 
   (* Check filenames don't contain a colon (limitation of qemu-img). *)
   if contains_colon indisk then
-    error "input filename '%s' contains a colon (':'); qemu-img command line syntax prevents us from using such an image" indisk;
+    error (f_"input filename '%s' contains a colon (':'); qemu-img command line syntax prevents us from using such an image") indisk;
 
   if contains_colon outdisk then
-    error "output filename '%s' contains a colon (':'); qemu-img command line syntax prevents us from using such an image" outdisk;
+    error (f_"output filename '%s' contains a colon (':'); qemu-img command line syntax prevents us from using such an image") outdisk;
 
   indisk, outdisk, compress, convert,
     debug_gc, format, ignores, machine_readable,
@@ -152,7 +154,7 @@ read the man page virt-sparsify(1).
 
 let () =
   if not quiet then
-    printf "Create overlay file to protect source disk ...\n%!"
+    printf (f_"Create overlay file to protect source disk ...\n%!")
 
 (* Create the temporary overlay file. *)
 let overlaydisk =
@@ -178,13 +180,13 @@ let overlaydisk =
   if verbose then
     printf "%s\n%!" cmd;
   if Sys.command cmd <> 0 then
-    error "external command failed: %s" cmd;
+    error (f_"external command failed: %s") cmd;
 
   tmp
 
 let () =
   if not quiet then
-    printf "Examine source disk ...\n%!"
+    printf (f_"Examine source disk ...\n%!")
 
 (* Connect to libguestfs. *)
 let g =
@@ -221,7 +223,7 @@ let () =
       if not (is_ignored fs) then (
         if List.mem fs zeroes then (
           if not quiet then
-            printf "Zeroing %s ...\n%!" fs;
+            printf (f_"Zeroing %s ...\n%!") fs;
 
           g#zero_device fs
         ) else (
@@ -231,7 +233,7 @@ let () =
 
           if mounted then (
             if not quiet then
-              printf "Fill free space in %s with zero ...\n%!" fs;
+              printf (f_"Fill free space in %s with zero ...\n%!") fs;
 
             g#zero_free_space "/"
           ) else (
@@ -247,7 +249,7 @@ let () =
 
             if is_linux_x86_swap then (
               if not quiet then
-                printf "Clearing Linux swap on %s ...\n%!" fs;
+                printf (f_"Clearing Linux swap on %s ...\n%!") fs;
 
               (* Don't use mkswap.  Just preserve the header containing
                * the label, UUID and swap format version (libguestfs
@@ -256,7 +258,7 @@ let () =
               let header = g#pread_device fs 4096 0L in
               g#zero_device fs;
               if g#pwrite_device fs header 0L <> 4096 then
-                error "pwrite: short write restoring swap partition header"
+                error (f_"pwrite: short write restoring swap partition header")
             )
           )
         );
@@ -282,7 +284,7 @@ let () =
 
         if created then (
           if not quiet then
-            printf "Fill free space in volgroup %s with zero ...\n%!" vg;
+            printf (f_"Fill free space in volgroup %s with zero ...\n%!") vg;
 
           g#zero_device lvdev;
           g#sync ();
@@ -313,11 +315,11 @@ let output_format =
       (match stat with
       | WEXITED 0 -> ()
       | WEXITED _ ->
-        error "external command failed: %s" cmd
+        error (f_"external command failed: %s") cmd
       | WSIGNALED i ->
-        error "external command '%s' killed by signal %d" cmd i
+        error (f_"external command '%s' killed by signal %d") cmd i
       | WSTOPPED i ->
-        error "external command '%s' stopped by signal %d" cmd i
+        error (f_"external command '%s' stopped by signal %d") cmd i
       );
       if string_prefix line "QEMU QCOW Image (v2)" then
         "qcow2"
@@ -331,7 +333,7 @@ let output_format =
  *)
 let () =
   if not quiet then
-    printf "Copy to destination and make sparse ...\n%!";
+    printf (f_"Copy to destination and make sparse ...\n%!");
 
   let cmd =
     sprintf "qemu-img convert -f qcow2 -O %s%s%s %s %s"
@@ -344,13 +346,13 @@ let () =
   if verbose then
     printf "%s\n%!" cmd;
   if Sys.command cmd <> 0 then
-    error "external command failed: %s" cmd
+    error (f_"external command failed: %s") cmd
 
 (* Finished. *)
 let () =
   if not quiet then (
     print_newline ();
-    wrap "Sparsify operation completed with no errors.  Before deleting the old disk, carefully check that the target disk boots and works correctly.\n";
+    wrap (s_"Sparsify operation completed with no errors.  Before deleting the old disk, carefully check that the target disk boots and works correctly.\n");
   );
 
   if debug_gc then
diff --git a/sparsify/utils.ml b/sparsify/utils.ml
index d2be12c..dd27ba8 100644
--- a/sparsify/utils.ml
+++ b/sparsify/utils.ml
@@ -22,6 +22,8 @@
 
 open Printf
 
+open Sparsify_gettext.Gettext
+
 module G = Guestfs
 
 let (//) = Filename.concat
@@ -104,7 +106,7 @@ let error fs =
     prerr_newline ();
     prerr_newline ();
     wrap ~chan:stderr
-      "If reporting bugs, run virt-sparsify with the '-v' and '-x' options and include the complete output.";
+      (s_"If reporting bugs, run virt-sparsify with the '-v' and '-x' options and include the complete output.");
     prerr_newline ();
     exit 1
   in
diff --git a/sysprep/Makefile.am b/sysprep/Makefile.am
index 2424071..38acfd2 100644
--- a/sysprep/Makefile.am
+++ b/sysprep/Makefile.am
@@ -31,6 +31,7 @@ CLEANFILES = \
 # Alphabetical order.
 SOURCES = \
 	main.ml \
+	sysprep_gettext.ml \
 	sysprep_operation.ml \
 	sysprep_operation.mli \
 	sysprep_operation_bash_history.ml \
@@ -62,6 +63,7 @@ if HAVE_OCAML
 
 # Note this list must be in dependency order.
 OBJECTS = \
+	sysprep_gettext.cmx \
 	utils.cmx \
 	sysprep_operation.cmx \
 	sysprep_operation_bash_history.cmx \
@@ -95,6 +97,9 @@ bin_SCRIPTS = virt-sysprep
 # option to be passed to gcc, so we don't try linking against an
 # installed copy of libguestfs.
 OCAMLPACKAGES = -package unix -I $(top_builddir)/src/.libs -I $(top_builddir)/ocaml
+if HAVE_OCAML_PKG_GETTEXT
+OCAMLPACKAGES += -package gettext-stub
+endif
 
 OCAMLCFLAGS = -g -warn-error CDEFLMPSUVYZX $(OCAMLPACKAGES)
 OCAMLOPTFLAGS = $(OCAMLCFLAGS)
diff --git a/sysprep/main.ml b/sysprep/main.ml
index 1223816..ca5664d 100644
--- a/sysprep/main.ml
+++ b/sysprep/main.ml
@@ -19,6 +19,8 @@
 open Unix
 open Printf
 
+open Sysprep_gettext.Gettext
+
 open Utils
 
 module G = Guestfs
@@ -53,7 +55,7 @@ let debug_gc, operations, g, selinux_relabel, quiet =
     files := (file, format) :: !files
   and set_domain dom =
     if !domain <> None then (
-      eprintf "%s: --domain option can only be given once\n" prog;
+      eprintf (f_"%s: --domain option can only be given once\n") prog;
       exit 1
     );
     domain := Some dom
@@ -65,11 +67,11 @@ let debug_gc, operations, g, selinux_relabel, quiet =
     exit 0
   and set_enable ops =
     if !operations <> None then (
-      eprintf "%s: --enable option can only be given once\n" prog;
+      eprintf (f_"%s: --enable option can only be given once\n") prog;
       exit 1
     );
     if ops = "" then (
-      eprintf "%s: you cannot pass an empty argument to --enable\n" prog;
+      eprintf (f_"%s: you cannot pass an empty argument to --enable\n") prog;
       exit 1
     );
     let ops = string_split "," ops in
@@ -77,7 +79,8 @@ let debug_gc, operations, g, selinux_relabel, quiet =
       fun opset op_name ->
         try Sysprep_operation.add_to_set op_name opset
         with Not_found ->
-          eprintf "%s: --enable: '%s' is not a known operation\n" prog op_name;
+          eprintf (f_"%s: --enable: '%s' is not a known operation\n")
+            prog op_name;
           exit 1
     ) Sysprep_operation.empty_set ops in
     operations := Some opset
@@ -91,38 +94,38 @@ let debug_gc, operations, g, selinux_relabel, quiet =
   in
 
   let basic_args = [
-    "-a",        Arg.String add_file,       "file Add disk image file";
-    "--add",     Arg.String add_file,       "file Add disk image file";
-    "-c",        Arg.Set_string libvirturi, "uri Set libvirt URI";
-    "--connect", Arg.Set_string libvirturi, "uri Set libvirt URI";
-    "--debug-gc", Arg.Set debug_gc,         " Debug GC and memory allocations (internal)";
-    "-d",        Arg.String set_domain,     "domain Set libvirt guest name";
-    "--domain",  Arg.String set_domain,     "domain Set libvirt guest name";
-    "-n",        Arg.Set dryrun,            " Perform a dry run";
-    "--dryrun",  Arg.Set dryrun,            " Perform a dry run";
-    "--dry-run", Arg.Set dryrun,            " Perform a dry run";
-    "--dump-pod", Arg.Unit dump_pod,        " Dump POD (internal)";
-    "--dump-pod-options", Arg.Unit dump_pod_options, " Dump POD for options (internal)";
-    "--enable",  Arg.String set_enable,     "operations Enable specific operations";
-    "--format",  Arg.Set_string format,     "format Set format (default: auto)";
-    "--list-operations", Arg.Unit list_operations, " List supported operations";
-    "-q",        Arg.Set quiet,             " Don't print log messages";
-    "--quiet",   Arg.Set quiet,             " Don't print log messages";
-    "--selinux-relabel", Arg.Unit force_selinux_relabel, " Force SELinux relabel";
-    "--no-selinux-relabel", Arg.Unit no_force_selinux_relabel, " Never do SELinux relabel";
-    "-v",        Arg.Set verbose,           " Enable debugging messages";
-    "--verbose", Arg.Set verbose,           " Enable debugging messages";
-    "-V",        Arg.Unit display_version,  " Display version and exit";
-    "--version", Arg.Unit display_version,  " Display version and exit";
-    "-x",        Arg.Set trace,             " Enable tracing of libguestfs calls";
+    "-a",        Arg.String add_file,       s_"file" ^ " " ^ s_"Add disk image file";
+    "--add",     Arg.String add_file,       s_"file" ^ " " ^ s_"Add disk image file";
+    "-c",        Arg.Set_string libvirturi, s_"uri" ^ " " ^ s_"Set libvirt URI";
+    "--connect", Arg.Set_string libvirturi, s_"uri" ^ " " ^ s_"Set libvirt URI";
+    "--debug-gc", Arg.Set debug_gc,         " " ^ s_"Debug GC and memory allocations (internal)";
+    "-d",        Arg.String set_domain,     s_"domain" ^ " " ^ s_"Set libvirt guest name";
+    "--domain",  Arg.String set_domain,     s_"domain" ^ " " ^ s_"Set libvirt guest name";
+    "-n",        Arg.Set dryrun,            " " ^ s_"Perform a dry run";
+    "--dryrun",  Arg.Set dryrun,            " " ^ s_"Perform a dry run";
+    "--dry-run", Arg.Set dryrun,            " " ^ s_"Perform a dry run";
+    "--dump-pod", Arg.Unit dump_pod,        " " ^ s_"Dump POD (internal)";
+    "--dump-pod-options", Arg.Unit dump_pod_options, " " ^ s_"Dump POD for options (internal)";
+    "--enable",  Arg.String set_enable,     s_"operations" ^ " " ^ s_"Enable specific operations";
+    "--format",  Arg.Set_string format,     s_"format" ^ " " ^ s_"Set format (default: auto)";
+    "--list-operations", Arg.Unit list_operations, " " ^ s_"List supported operations";
+    "-q",        Arg.Set quiet,             " " ^ s_"Don't print log messages";
+    "--quiet",   Arg.Set quiet,             " " ^ s_"Don't print log messages";
+    "--selinux-relabel", Arg.Unit force_selinux_relabel, " " ^ s_"Force SELinux relabel";
+    "--no-selinux-relabel", Arg.Unit no_force_selinux_relabel, " " ^ s_"Never do SELinux relabel";
+    "-v",        Arg.Set verbose,           " " ^ s_"Enable debugging messages";
+    "--verbose", Arg.Set verbose,           " " ^ s_"Enable debugging messages";
+    "-V",        Arg.Unit display_version,  " " ^ s_"Display version and exit";
+    "--version", Arg.Unit display_version,  " " ^ s_"Display version and exit";
+    "-x",        Arg.Set trace,             " " ^ s_"Enable tracing of libguestfs calls";
   ] in
   let args = basic_args @ Sysprep_operation.extra_args () in
   let args =
     List.sort (fun (a,_,_) (b,_,_) -> compare_command_line_args a b) args in
   let argspec = Arg.align args in
-  let anon_fun _ = raise (Arg.Bad "extra parameter on the command line") in
+  let anon_fun _ = raise (Arg.Bad (s_"extra parameter on the command line")) in
   let usage_msg =
-    sprintf "\
+    sprintf (f_"\
 %s: reset or unconfigure a virtual machine so clones can be made
 
  virt-sysprep [--options] -d domname
@@ -131,7 +134,7 @@ let debug_gc, operations, g, selinux_relabel, quiet =
 
 A short summary of the options is given below.  For detailed help please
 read the man page virt-sysprep(1).
-"
+")
       prog in
   Arg.parse argspec anon_fun usage_msg;
 
@@ -142,8 +145,8 @@ read the man page virt-sysprep(1).
   let add =
     match files, domain with
     | [], None ->
-      eprintf "%s: you must give either -a or -d options\n" prog;
-      eprintf "Read virt-sysprep(1) man page for further information.\n";
+      eprintf (f_"%s: you must give either -a or -d options\n") prog;
+      eprintf (f_"Read virt-sysprep(1) man page for further information.\n");
       exit 1
     | [], Some dom ->
       fun (g : Guestfs.guestfs) readonly ->
@@ -151,8 +154,8 @@ read the man page virt-sysprep(1).
         let readonlydisk = "ignore" (* ignore CDs, data drives *) in
         ignore (g#add_domain ~readonly ?libvirturi ~allowuuid ~readonlydisk dom)
     | _, Some _ ->
-      eprintf "%s: you cannot give -a and -d options together\n" prog;
-      eprintf "Read virt-sysprep(1) man page for further information.\n";
+      eprintf (f_"%s: you cannot give -a and -d options together\n") prog;
+      eprintf (f_"Read virt-sysprep(1) man page for further information.\n");
       exit 1
     | files, None ->
       fun g readonly ->
@@ -172,7 +175,7 @@ read the man page virt-sysprep(1).
   let verbose = !verbose in
 
   if not quiet then
-    printf "Examining the guest ...\n%!";
+    printf (f_"Examining the guest ...\n%!");
 
   (* Connect to libguestfs. *)
   let g = new G.guestfs () in
@@ -187,7 +190,7 @@ let () =
   (* Inspection. *)
   match Array.to_list (g#inspect_os ()) with
   | [] ->
-    eprintf "%s: no operating systems were found in the guest image\n" prog;
+    eprintf (f_"%s: no operating systems were found in the guest image\n") prog;
     exit 1
   | roots ->
     List.iter (
@@ -201,7 +204,7 @@ let () =
         List.iter (
           fun (mp, dev) ->
             try g#mount dev mp
-            with Guestfs.Error msg -> eprintf "%s (ignored)\n" msg
+            with Guestfs.Error msg -> eprintf (f_"%s (ignored)\n") msg
         ) mps;
 
         (* Perform the operations. *)
diff --git a/sysprep/sysprep_operation.ml b/sysprep/sysprep_operation.ml
index d3a12fe..e22d3a2 100644
--- a/sysprep/sysprep_operation.ml
+++ b/sysprep/sysprep_operation.ml
@@ -20,6 +20,8 @@ open Utils
 
 open Printf
 
+open Sysprep_gettext.Gettext
+
 type flag = [ `Created_files ]
 
 type operation = {
@@ -66,7 +68,7 @@ and check_no_dupes ops =
     List.fold_left (
       fun opset op ->
         if OperationSet.mem op opset then (
-          eprintf "virt-sysprep: duplicate operation name (%s)\n" op.name;
+          eprintf (f_"virt-sysprep: duplicate operation name (%s)\n") op.name;
           exit 1
         );
         add_to_set op.name opset
@@ -75,23 +77,24 @@ and check_no_dupes ops =
 and check op =
   let n = String.length op.name in
   if n = 0 then (
-    eprintf "virt-sysprep: operation name is an empty string\n";
+    eprintf (f_"virt-sysprep: operation name is an empty string\n");
     exit 1;
   );
   for i = 0 to n-1 do
     match String.unsafe_get op.name i with
     | 'a'..'z' | 'A'..'Z' | '0'..'9' | '-' -> ()
     | c ->
-      eprintf "virt-sysprep: disallowed character (%c) in operation name\n" c;
+      eprintf (f_"virt-sysprep: disallowed character (%c) in operation name\n")
+        c;
       exit 1
   done;
   let n = String.length op.heading in
   if n = 0 then (
-    eprintf "virt-sysprep: operation %s has no heading\n" op.name;
+    eprintf (f_"virt-sysprep: operation %s has no heading\n") op.name;
     exit 1
   );
   if op.heading.[n-1] = '\n' || op.heading.[n-1] = '.' then (
-    eprintf "virt-sysprep: heading for %s must not end with newline or period\n"
+    eprintf (f_"virt-sysprep: heading for %s must not end with newline or period\n")
       op.name;
     exit 1
   );
@@ -100,11 +103,12 @@ and check op =
   | Some description ->
     let n = String.length description in
     if n = 0 then (
-      eprintf "virt-sysprep: operation %s has no POD\n" op.name;
+      eprintf (f_"virt-sysprep: operation %s has no POD\n") op.name;
       exit 1
     );
     if description.[n-1] = '\n' then (
-      eprintf "virt-sysprep: POD for %s must not end with newline\n" op.name;
+      eprintf (f_"virt-sysprep: POD for %s must not end with newline\n")
+        op.name;
       exit 1
     )
   )
diff --git a/sysprep/sysprep_operation_bash_history.ml b/sysprep/sysprep_operation_bash_history.ml
index dbd6c50..56fd9f3 100644
--- a/sysprep/sysprep_operation_bash_history.ml
+++ b/sysprep/sysprep_operation_bash_history.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -35,10 +36,10 @@ let bash_history_perform g root =
 let bash_history_op = {
   name = "bash-history";
   enabled_by_default = true;
-  heading = "Remove the bash history in the guest";
-  pod_description = Some "\
+  heading = s_"Remove the bash history in the guest";
+  pod_description = Some (s_"\
 Remove the bash history of user \"root\" and any other users
-who have a C<.bash_history> file in their home directory.";
+who have a C<.bash_history> file in their home directory.");
   extra_args = [];
   perform = bash_history_perform;
 }
diff --git a/sysprep/sysprep_operation_cron_spool.ml b/sysprep/sysprep_operation_cron_spool.ml
index daa3c68..20f1b20 100644
--- a/sysprep/sysprep_operation_cron_spool.ml
+++ b/sysprep/sysprep_operation_cron_spool.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -27,7 +28,7 @@ let cron_spool_perform g root =
 let cron_spool_op = {
   name = "cron-spool";
   enabled_by_default = true;
-  heading = "Remove user at-jobs and cron-jobs";
+  heading = s_"Remove user at-jobs and cron-jobs";
   pod_description = None;
   extra_args = [];
   perform = cron_spool_perform;
diff --git a/sysprep/sysprep_operation_dhcp_client_state.ml b/sysprep/sysprep_operation_dhcp_client_state.ml
index 279893d..585424c 100644
--- a/sysprep/sysprep_operation_dhcp_client_state.ml
+++ b/sysprep/sysprep_operation_dhcp_client_state.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -32,7 +33,7 @@ let dhcp_client_state_perform g root =
 let dhcp_client_state_op = {
   name = "dhcp-client-state";
   enabled_by_default = true;
-  heading = "Remove DHCP client leases";
+  heading = s_"Remove DHCP client leases";
   pod_description = None;
   extra_args = [];
   perform = dhcp_client_state_perform;
diff --git a/sysprep/sysprep_operation_dhcp_server_state.ml b/sysprep/sysprep_operation_dhcp_server_state.ml
index b28aa69..7629dee 100644
--- a/sysprep/sysprep_operation_dhcp_server_state.ml
+++ b/sysprep/sysprep_operation_dhcp_server_state.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -27,7 +28,7 @@ let dhcp_server_state_perform g root =
 let dhcp_server_state_op = {
   name = "dhcp-server-state";
   enabled_by_default = true;
-  heading = "Remove DHCP server leases";
+  heading = s_"Remove DHCP server leases";
   pod_description = None;
   extra_args = [];
   perform = dhcp_server_state_perform;
diff --git a/sysprep/sysprep_operation_dovecot_data.ml b/sysprep/sysprep_operation_dovecot_data.ml
index 65825ba..14cbe8d 100644
--- a/sysprep/sysprep_operation_dovecot_data.ml
+++ b/sysprep/sysprep_operation_dovecot_data.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -34,7 +35,7 @@ let dovecot_data_perform g root =
 let dovecot_data_op = {
   name = "dovecot-data";
   enabled_by_default = true;
-  heading = "Remove Dovecot (mail server) data";
+  heading = s_"Remove Dovecot (mail server) data";
   pod_description = None;
   extra_args = [];
   perform = dovecot_data_perform;
diff --git a/sysprep/sysprep_operation_flag_reconfiguration.ml b/sysprep/sysprep_operation_flag_reconfiguration.ml
index 755b1d0..0efee02 100644
--- a/sysprep/sysprep_operation_flag_reconfiguration.ml
+++ b/sysprep/sysprep_operation_flag_reconfiguration.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -31,10 +32,10 @@ let flag_reconfiguration g root =
 let flag_reconfiguration_op = {
   name = "flag-reconfiguration";
   enabled_by_default = false;
-  heading = "Flag the system for reconfiguration";
-  pod_description = Some "\
+  heading = s_"Flag the system for reconfiguration";
+  pod_description = Some (s_"\
 Note that this may require user intervention when the
-guest is booted.";
+guest is booted.");
   extra_args = [];
   perform = flag_reconfiguration;
 }
diff --git a/sysprep/sysprep_operation_hostname.ml b/sysprep/sysprep_operation_hostname.ml
index 395691c..f832a18 100644
--- a/sysprep/sysprep_operation_hostname.ml
+++ b/sysprep/sysprep_operation_hostname.ml
@@ -20,6 +20,7 @@ open Printf
 
 open Utils
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -53,16 +54,16 @@ let hostname_perform g root =
 let hostname_op = {
   name = "hostname";
   enabled_by_default = true;
-  heading = "Change the hostname of the guest";
-  pod_description = Some "\
+  heading = s_"Change the hostname of the guest";
+  pod_description = Some (s_"\
 This operation changes the hostname of the guest to the value
 given in the I<--hostname> parameter.
 
 If the I<--hostname> parameter is not given, then the hostname is changed
-to C<localhost.localdomain>.";
+to C<localhost.localdomain>.");
   extra_args = [
-    ("--hostname", Arg.Set_string hostname, "hostname New hostname"),
-    "\
+    ("--hostname", Arg.Set_string hostname, s_"hostname" ^ " " ^ s_"New hostname"),
+    s_"\
 Change the hostname.  If not given, defaults to C<localhost.localdomain>."
   ];
   perform = hostname_perform;
diff --git a/sysprep/sysprep_operation_logfiles.ml b/sysprep/sysprep_operation_logfiles.ml
index cf72803..aa3c986 100644
--- a/sysprep/sysprep_operation_logfiles.ml
+++ b/sysprep/sysprep_operation_logfiles.ml
@@ -19,6 +19,7 @@
 open Printf
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -81,12 +82,12 @@ let logfiles_perform g root =
 let logfiles_op = {
   name = "logfiles";
   enabled_by_default = true;
-  heading = "Remove many log files from the guest";
+  heading = s_"Remove many log files from the guest";
   pod_description = Some (
-    sprintf "\
+    sprintf (f_"\
 On Linux the following files are removed:
 
-%s" globs_as_pod);
+%s") globs_as_pod);
   extra_args = [];
   perform = logfiles_perform;
 }
diff --git a/sysprep/sysprep_operation_mail_spool.ml b/sysprep/sysprep_operation_mail_spool.ml
index 25f4272..11dd292 100644
--- a/sysprep/sysprep_operation_mail_spool.ml
+++ b/sysprep/sysprep_operation_mail_spool.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -32,7 +33,7 @@ let mail_spool_perform g root =
 let mail_spool_op = {
   name = "mail-spool";
   enabled_by_default = true;
-  heading = "Remove email from the local mail spool directory";
+  heading = s_"Remove email from the local mail spool directory";
   pod_description = None;
   extra_args = [];
   perform = mail_spool_perform;
diff --git a/sysprep/sysprep_operation_net_hwaddr.ml b/sysprep/sysprep_operation_net_hwaddr.ml
index 57ca2b9..5277017 100644
--- a/sysprep/sysprep_operation_net_hwaddr.ml
+++ b/sysprep/sysprep_operation_net_hwaddr.ml
@@ -18,6 +18,7 @@
 
 open Utils
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -45,10 +46,10 @@ let net_hwaddr_perform g root =
 let net_hwaddr_op = {
   name = "net-hwaddr";
   enabled_by_default = true;
-  heading = "Remove HWADDR (hard-coded MAC address) configuration";
-  pod_description = Some "\
+  heading = s_"Remove HWADDR (hard-coded MAC address) configuration";
+  pod_description = Some (s_"\
 For Fedora and Red Hat Enterprise Linux,
-this is removed from C<ifcfg-*> files.";
+this is removed from C<ifcfg-*> files.");
   extra_args = [];
   perform = net_hwaddr_perform;
 }
diff --git a/sysprep/sysprep_operation_package_manager_cache.ml b/sysprep/sysprep_operation_package_manager_cache.ml
index 80eefa3..957b10d 100644
--- a/sysprep/sysprep_operation_package_manager_cache.ml
+++ b/sysprep/sysprep_operation_package_manager_cache.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -36,7 +37,7 @@ let package_manager_cache_perform g root =
 let package_manager_cache_op = {
   name = "package-manager-cache";
   enabled_by_default = true;
-  heading = "Remove package manager cache";
+  heading = s_"Remove package manager cache";
   pod_description = None;
   extra_args = [];
   perform = package_manager_cache_perform;
diff --git a/sysprep/sysprep_operation_random_seed.ml b/sysprep/sysprep_operation_random_seed.ml
index c6e8d85..5fbe9a4 100644
--- a/sysprep/sysprep_operation_random_seed.ml
+++ b/sysprep/sysprep_operation_random_seed.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -46,12 +47,12 @@ let random_seed_perform g root =
 let random_seed_op = {
   name = "random-seed";
   enabled_by_default = true;
-  heading = "Generate random seed for guest";
-  pod_description = Some "\
+  heading = s_"Generate random seed for guest";
+  pod_description = Some (s_"\
 Write some random bytes from the host into the random seed file of the
 guest.
 
-See L</RANDOM SEED> below.";
+See L</RANDOM SEED> below.");
   extra_args = [];
   perform = random_seed_perform;
 }
diff --git a/sysprep/sysprep_operation_rhn_systemid.ml b/sysprep/sysprep_operation_rhn_systemid.ml
index 9d9f2f8..c86de7a 100644
--- a/sysprep/sysprep_operation_rhn_systemid.ml
+++ b/sysprep/sysprep_operation_rhn_systemid.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -33,7 +34,7 @@ let rhn_systemid_perform g root =
 let rhn_systemid_op = {
   name = "rhn-systemid";
   enabled_by_default = true;
-  heading = "Remove the RHN system ID";
+  heading = s_"Remove the RHN system ID";
   pod_description = None;
   extra_args = [];
   perform = rhn_systemid_perform;
diff --git a/sysprep/sysprep_operation_samba_db_log.ml b/sysprep/sysprep_operation_samba_db_log.ml
index a02ba9d..6bd6799 100644
--- a/sysprep/sysprep_operation_samba_db_log.ml
+++ b/sysprep/sysprep_operation_samba_db_log.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -43,7 +44,7 @@ let samba_db_log_perform g root =
 let samba_db_log_op = {
   name = "samba-db-log";
   enabled_by_default = true;
-  heading = "Remove the database and log files of Samba";
+  heading = s_"Remove the database and log files of Samba";
   pod_description = None;
   extra_args = [];
   perform = samba_db_log_perform;
diff --git a/sysprep/sysprep_operation_script.ml b/sysprep/sysprep_operation_script.ml
index bcbba73..1f33c05 100644
--- a/sysprep/sysprep_operation_script.ml
+++ b/sysprep/sysprep_operation_script.ml
@@ -21,13 +21,14 @@ open Unix
 
 open Utils
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
 let scriptdir = ref None
 let set_scriptdir dir =
   if !scriptdir <> None then (
-    eprintf "virt-sysprep: --scriptdir cannot be used more than once\n";
+    eprintf (f_"virt-sysprep: --scriptdir cannot be used more than once\n");
     exit 1
   );
   scriptdir := Some dir
@@ -61,17 +62,17 @@ let rec script_perform (g : Guestfs.guestfs) root =
       match snd (waitpid [] pid) with
       | WEXITED 0 -> true
       | WEXITED i ->
-        eprintf "virt-sysprep: script: failed (code %d)\n" i;
+        eprintf (f_"virt-sysprep: script: failed (code %d)\n") i;
         false
       | WSIGNALED i
       | WSTOPPED i ->
-        eprintf "virt-sysprep: script: killed by signal (%d)\n" i;
+        eprintf (f_"virt-sysprep: script: killed by signal (%d)\n") i;
         false in
 
     (* Remote temporary directory / mountpoint. *)
     if cleanup then rmdir scriptdir;
 
-    if not ok then failwith "script failed"
+    if not ok then failwith (s_"script failed")
   );
   []
 
@@ -114,8 +115,8 @@ trap cleanup INT TERM QUIT EXIT ERR\n"
 let script_op = {
   name = "script";
   enabled_by_default = true;
-  heading = "Run arbitrary scripts against the guest";
-  pod_description = Some "\
+  heading = s_"Run arbitrary scripts against the guest";
+  pod_description = Some (s_"\
 The C<script> module lets you run arbitrary shell scripts or programs
 against the guest.
 
@@ -133,10 +134,10 @@ guest's DNS configuration file, but C<rm /etc/resolv.conf> would
 (try to) remove the host's file.
 
 Normally a temporary mount point for the guest is used, but you
-can choose a specific one by using the I<--scriptdir> parameter.";
+can choose a specific one by using the I<--scriptdir> parameter.");
   extra_args = [
-    ("--scriptdir", Arg.String set_scriptdir, "dir Mount point on host"),
-    "\
+    ("--scriptdir", Arg.String set_scriptdir, s_"dir" ^ " " ^ s_"Mount point on host"),
+    s_"\
 The mount point (an empty directory on the host) used when
 the C<script> operation is enabled and one or more scripts
 are specified using I<--script> parameter(s).
@@ -145,8 +146,8 @@ B<Note:> C<scriptdir> B<must> be an absolute path.
 
 If I<--scriptdir> is not specified then a temporary mountpoint
 will be created.";
-    ("--script", Arg.String add_script, "script Script or program to run on guest"),
-    "\
+    ("--script", Arg.String add_script, s_"script" ^ " " ^ s_"Script or program to run on guest"),
+    s_"\
 Run the named C<script> (a shell script or program) against the
 guest.  The script can be any program on the host.  The script's
 current directory will be the guest's root directory.
diff --git a/sysprep/sysprep_operation_smolt_uuid.ml b/sysprep/sysprep_operation_smolt_uuid.ml
index 85e788e..25f7372 100644
--- a/sysprep/sysprep_operation_smolt_uuid.ml
+++ b/sysprep/sysprep_operation_smolt_uuid.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -37,7 +38,7 @@ let smolt_uuid_perform g root =
 let smolt_uuid_op = {
   name = "smolt-uuid";
   enabled_by_default = true;
-  heading = "Remove the Smolt hardware UUID";
+  heading = s_"Remove the Smolt hardware UUID";
   pod_description = None;
   extra_args = [];
   perform = smolt_uuid_perform;
diff --git a/sysprep/sysprep_operation_ssh_hostkeys.ml b/sysprep/sysprep_operation_ssh_hostkeys.ml
index db45b44..62ea32e 100644
--- a/sysprep/sysprep_operation_ssh_hostkeys.ml
+++ b/sysprep/sysprep_operation_ssh_hostkeys.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -32,8 +33,8 @@ let ssh_hostkeys_perform g root =
 let ssh_hostkeys_op = {
   name = "ssh-hostkeys";
   enabled_by_default = true;
-  heading = "Remove the SSH host keys in the guest";
-  pod_description = Some "\
+  heading = s_"Remove the SSH host keys in the guest";
+  pod_description = Some (s_"\
 The SSH host keys are regenerated (differently) next time the guest is
 booted.
 
@@ -43,7 +44,7 @@ you a stark warning about the host key changing:
  @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  @    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
  @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
- IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!";
+ IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!");
   extra_args = [];
   perform = ssh_hostkeys_perform;
 }
diff --git a/sysprep/sysprep_operation_ssh_userdir.ml b/sysprep/sysprep_operation_ssh_userdir.ml
index 9ea1017..3141c9e 100644
--- a/sysprep/sysprep_operation_ssh_userdir.ml
+++ b/sysprep/sysprep_operation_ssh_userdir.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -35,10 +36,10 @@ let ssh_userdir_perform g root =
 let ssh_userdir_op = {
   name = "ssh-userdir";
   enabled_by_default = true;
-  heading = "Remove \".ssh\" directories in the guest";
-  pod_description = Some "\
+  heading = s_"Remove \".ssh\" directories in the guest";
+  pod_description = Some (s_"\
 Remove the C<.ssh> directory of user \"root\" and any other
-users who have a C<.ssh> directory in their home directory.";
+users who have a C<.ssh> directory in their home directory.");
   extra_args = [];
   perform = ssh_userdir_perform;
 }
diff --git a/sysprep/sysprep_operation_sssd_db_log.ml b/sysprep/sysprep_operation_sssd_db_log.ml
index 79e6950..8b1923a 100644
--- a/sysprep/sysprep_operation_sssd_db_log.ml
+++ b/sysprep/sysprep_operation_sssd_db_log.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -41,7 +42,7 @@ let sssd_db_log_perform g root =
 let sssd_db_log_op = {
   name = "sssd-db-log";
   enabled_by_default = true;
-  heading = "Remove the database and log files of sssd";
+  heading = s_"Remove the database and log files of sssd";
   pod_description = None;
   extra_args = [];
   perform = sssd_db_log_perform;
diff --git a/sysprep/sysprep_operation_udev_persistent_net.ml b/sysprep/sysprep_operation_udev_persistent_net.ml
index 95133d1..6de2589 100644
--- a/sysprep/sysprep_operation_udev_persistent_net.ml
+++ b/sysprep/sysprep_operation_udev_persistent_net.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -32,15 +33,15 @@ let udev_persistent_net_perform g root =
 let udev_persistent_net_op = {
   name = "udev-persistent-net";
   enabled_by_default = true;
-  heading = "Remove udev persistent net rules";
-  pod_description = Some "\
+  heading = s_"Remove udev persistent net rules";
+  pod_description = Some (s_"\
 Remove udev persistent net rules which map the guest's existing MAC
 address to a fixed ethernet device (eg. eth0).
 
 After a guest is cloned, the MAC address usually changes.  Since the
 old MAC address occupies the old name (eg. eth0), this means the fresh
 MAC address is assigned to a new name (eg. eth1) and this is usually
-undesirable.  Erasing the udev persistent net rules avoids this.";
+undesirable.  Erasing the udev persistent net rules avoids this.");
   extra_args = [];
   perform = udev_persistent_net_perform;
 }
diff --git a/sysprep/sysprep_operation_user_account.ml b/sysprep/sysprep_operation_user_account.ml
index 6375705..02201eb 100644
--- a/sysprep/sysprep_operation_user_account.ml
+++ b/sysprep/sysprep_operation_user_account.ml
@@ -18,9 +18,11 @@
 
 open Printf
 
-open Sysprep_operation
 open Utils
 
+open Sysprep_operation
+open Sysprep_gettext.Gettext
+
 module G = Guestfs
 
 let user_account_perform g root =
@@ -59,10 +61,10 @@ let user_account_perform g root =
 let user_account_op = {
   name = "user-account";
   enabled_by_default = false;
-  heading = "Remove the user accounts in the guest";
-  pod_description = Some "\
+  heading = s_"Remove the user accounts in the guest";
+  pod_description = Some (s_"\
 Remove all the user accounts and their home directories.
-The \"root\" account is not removed.";
+The \"root\" account is not removed.");
   extra_args = [];
   perform = user_account_perform;
 }
diff --git a/sysprep/sysprep_operation_utmp.ml b/sysprep/sysprep_operation_utmp.ml
index cdcf01f..c4a548d 100644
--- a/sysprep/sysprep_operation_utmp.ml
+++ b/sysprep/sysprep_operation_utmp.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -31,11 +32,11 @@ let utmp_perform g root =
 let utmp_op = {
   name = "utmp";
   enabled_by_default = true;
-  heading = "Remove the utmp file";
-  pod_description = Some "\
+  heading = s_"Remove the utmp file";
+  pod_description = Some (s_"\
 This file records who is currently logged in on a machine.  In modern
 Linux distros it is stored in a ramdisk and hence not part of the
-virtual machine's disk, but it was stored on disk in older distros.";
+virtual machine's disk, but it was stored on disk in older distros.");
   extra_args = [];
   perform = utmp_perform;
 }
diff --git a/sysprep/sysprep_operation_yum_uuid.ml b/sysprep/sysprep_operation_yum_uuid.ml
index cc5fec1..ec69ca5 100644
--- a/sysprep/sysprep_operation_yum_uuid.ml
+++ b/sysprep/sysprep_operation_yum_uuid.ml
@@ -17,6 +17,7 @@
  *)
 
 open Sysprep_operation
+open Sysprep_gettext.Gettext
 
 module G = Guestfs
 
@@ -31,10 +32,10 @@ let yum_uuid_perform g root =
 let yum_uuid_op = {
   name = "yum-uuid";
   enabled_by_default = true;
-  heading = "Remove the yum UUID";
-  pod_description = Some "\
+  heading = s_"Remove the yum UUID";
+  pod_description = Some (s_"\
 Yum creates a fresh UUID the next time it runs when it notices that the
-original UUID has been erased.";
+original UUID has been erased.");
   extra_args = [];
   perform = yum_uuid_perform;
 }
-- 
1.7.10




More information about the Libguestfs mailing list