diff --git a/configure.ac b/configure.ac
index 8dd4e26..68bf01f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -128,6 +128,7 @@ AC_CONFIG_FILES([
 	doc/Makefile
 	gl/Makefile
 	po/Makefile.in
+	po/shared-catalog
 	src/Makefile
 	src/skels/Makefile
 	src/tests/Makefile
diff --git a/po/Makevars b/po/Makevars
index e4b9263..4431035 100644
--- a/po/Makevars
+++ b/po/Makevars
@@ -25,7 +25,7 @@ COPYRIGHT_HOLDER = Free Software Foundation, Inc.
 # Possible values are "yes", "no", or empty.  If it is empty, try to
 # detect it automatically by scanning the files in $(top_srcdir) for
 # "GNU packagename" string.
-PACKAGE_GNU =
+PACKAGE_GNU = yes
 
 # This is the email address or URL to which the translators shall report
 # bugs in the untranslated strings:
@@ -83,3 +83,8 @@ XGETTEXT_OPTIONS += --directory=$(top_builddir) src/cmdline.c
 
 ../src/cmdline.c: $(top_srcdir)/src/cmdline.ggo
 	$(MAKE) $(AM_MAKEFLAGS) -C ../src cmdline.c
+
+# Additional makefile for shared catalog
+include shared-catalog
+
+XGETEXT_OPTIONS_FOR_SKELS=
diff --git a/po/POTSKELS.in b/po/POTSKELS.in
new file mode 100644
index 0000000..a4bfa89
--- /dev/null
+++ b/po/POTSKELS.in
@@ -0,0 +1,6 @@
+# List of skeletons files containing translatable strings.
+src/skels/c_source.h_skel
+src/skels/custom_getopt_gen.h_skel
+src/skels/dependant_option.h_skel
+src/skels/group_option.h_skel
+src/skels/required_option.h_skel
\ No newline at end of file
diff --git a/po/gengetopt-shared.pot b/po/gengetopt-shared.pot
new file mode 100644
index 0000000..7a249cc
--- /dev/null
+++ b/po/gengetopt-shared.pot
@@ -0,0 +1,213 @@
+# SOME DESCRIPTIVE TITLE.
+# This file is put in the public domain.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU gengetopt 2.23\n"
+"Report-Msgid-Bugs-To: bug-gengetopt@gnu.org\n"
+"POT-Creation-Date: 2020-04-27 11:39+0800\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
+
+#: skels/c_source.h_skel:42
+msgid "@purpose@"
+msgstr ""
+
+#: skels/c_source.h_skel:44
+msgid "@usage_string@"
+msgstr ""
+
+#: skels/c_source.h_skel:46
+msgid "@versiontext@"
+msgstr ""
+
+#: skels/c_source.h_skel:48
+msgid "@description@"
+msgstr ""
+
+#: skels/c_source.h_skel:195
+#, c-format
+msgid "Usage: %s %s\n"
+msgstr ""
+
+#: skels/c_source.h_skel:453
+#, c-format
+msgid "%s=\"%s\" # %s\n"
+msgstr ""
+
+#: skels/c_source.h_skel:455 skels/c_source.h_skel:457
+#, c-format
+msgid "%s=\"%s\"\n"
+msgstr ""
+
+#: skels/c_source.h_skel:460
+#, c-format
+msgid "%s\n"
+msgstr ""
+
+#: skels/c_source.h_skel:482
+#, c-format
+msgid "%s: cannot dump options to stream\n"
+msgstr ""
+
+#: skels/c_source.h_skel:502
+#, c-format
+msgid "%s: cannot open file for writing: %s\n"
+msgstr ""
+
+#: skels/c_source.h_skel:635
+#, c-format
+msgid "%s: %s option occurrences must be %d\n"
+msgstr ""
+
+#: skels/c_source.h_skel:644
+#, c-format
+msgid "%s: %s option occurrences must be between %d and %d\n"
+msgstr ""
+
+#: skels/c_source.h_skel:654
+#, c-format
+msgid "%s: %s option occurrences must be at least %d\n"
+msgstr ""
+
+#: skels/c_source.h_skel:664
+#, c-format
+msgid "%s: %s option occurrences must be at most %d\n"
+msgstr ""
+
+#: skels/c_source.h_skel:791
+#, c-format
+msgid "%s: `--%s' (`-%c') option given more than once%s\n"
+msgstr ""
+
+#: skels/c_source.h_skel:795
+#, c-format
+msgid "%s: `--%s' option given more than once%s\n"
+msgstr ""
+
+#: skels/c_source.h_skel:805
+#, c-format
+msgid "%s: %s argument, \"%s\", for option `--%s' (`-%c')%s\n"
+msgstr ""
+
+#: skels/c_source.h_skel:806 skels/c_source.h_skel:810
+msgid "ambiguous"
+msgstr ""
+
+#: skels/c_source.h_skel:806 skels/c_source.h_skel:810
+msgid "invalid"
+msgstr ""
+
+#: skels/c_source.h_skel:809
+#, c-format
+msgid "%s: %s argument, \"%s\", for option `--%s'%s\n"
+msgstr ""
+
+#: skels/c_source.h_skel:918
+#, c-format
+msgid "%s: invalid numeric value: %s\n"
+msgstr ""
+
+#: skels/c_source.h_skel:1238
+#, c-format
+msgid "%s: option %s conflicts with option %s\n"
+msgstr ""
+
+#: skels/c_source.h_skel:1339
+#, c-format
+msgid "%s: option unknown: %c%s\n"
+msgstr ""
+
+#: skels/c_source.h_skel:1428
+#, c-format
+msgid "%s: Error opening configuration file '%s'\n"
+msgstr ""
+
+#: skels/c_source.h_skel:1440
+#, c-format
+msgid "%s:%s:%d: Line too long in configuration file\n"
+msgstr ""
+
+#: skels/c_source.h_skel:1490
+#, c-format
+msgid "%s:%s:%d: unterminated string in configuration file\n"
+msgstr ""
+
+#: skels/c_source.h_skel:1513
+#, c-format
+msgid "%s:%s:%d: malformed string in configuration file\n"
+msgstr ""
+
+#: skels/c_source.h_skel:1525
+#, c-format
+msgid "%s:%s:%d: include requires a filename argument.\n"
+msgstr ""
+
+#: skels/c_source.h_skel:1596
+msgid " in configuration file "
+msgstr ""
+
+#: skels/custom_getopt_gen.h_skel:328
+#, c-format
+msgid "%s: option `%s' is ambiguous\n"
+msgstr ""
+
+#: skels/custom_getopt_gen.h_skel:346
+#, c-format
+msgid "%s: option `--%s' doesn't allow an argument\n"
+msgstr ""
+
+#: skels/custom_getopt_gen.h_skel:350
+#, c-format
+msgid "%s: option `%c%s' doesn't allow an argument\n"
+msgstr ""
+
+#: skels/custom_getopt_gen.h_skel:365
+#, c-format
+msgid "%s: option `%s' requires an argument\n"
+msgstr ""
+
+#: skels/custom_getopt_gen.h_skel:392
+#, c-format
+msgid "%s: unrecognized option `--%s'\n"
+msgstr ""
+
+#: skels/custom_getopt_gen.h_skel:397
+#, c-format
+msgid "%s: unrecognized option `%c%s'\n"
+msgstr ""
+
+#: skels/custom_getopt_gen.h_skel:419
+#, c-format
+msgid "%s: invalid option -- %c\n"
+msgstr ""
+
+#: skels/custom_getopt_gen.h_skel:446
+#, c-format
+msgid "%s: option requires an argument -- %c\n"
+msgstr ""
+
+#: skels/dependant_option.h_skel:3
+#, c-format
+msgid "%s: %s option depends on option '%s'%s\n"
+msgstr ""
+
+#: skels/group_option.h_skel:3
+#, c-format
+msgid "%s: %d option of group %s was given. %s is required%s.\n"
+msgid_plural "%s: %d options of group %s were given. %s is required%s.\n"
+msgstr[0] ""
+msgstr[1] ""
+
+#: skels/required_option.h_skel:8
+#, c-format
+msgid "%s: %s option required%s\n"
+msgstr ""
diff --git a/po/shared-catalog.in b/po/shared-catalog.in
new file mode 100644
index 0000000..746d326
--- /dev/null
+++ b/po/shared-catalog.in
@@ -0,0 +1,117 @@
+POTSKELFILES = \
+	skels/c_source.h_skel \
+	skels/custom_getopt_gen.h_skel \
+	skels/dependant_option.h_skel \
+	skels/group_option.h_skel \
+	skels/required_option.h_skel
+
+POSKELFILESDEPS_ = $(srcdir)/$(DOMAIN)-shared.pot
+POSKELFILESDEPS_yes = $(POSKELFILESDEPS_)
+POSKELFILESDEPS_no =
+POSKELFILESDEPS = $(POSKELFILESDEPS_$(PO_DEPENDS_ON_POT))
+
+# $(srcdir)/$(DOMAIN).pot is only created when needed. When xgettext finds no
+# internationalized messages, no $(srcdir)/$(DOMAIN).pot is created (because
+# we don't want to bother translators with empty POT files). We assume that
+# LINGUAS is empty in this case, i.e. $(POFILES) and $(GMOFILES) are empty.
+# In this case, stamp-po is a nop (i.e. a phony target).
+
+# stamp-po is a timestamp denoting the last time at which the CATALOGS have
+# been loosely updated. Its purpose is that when a developer or translator
+# checks out the package via CVS, and the $(DOMAIN).pot file is not in CVS,
+# "make" will update the $(DOMAIN).pot and the $(CATALOGS), but subsequent
+# invocations of "make" will do nothing. This timestamp would not be necessary
+# if updating the $(CATALOGS) would always touch them; however, the rule for
+# $(POFILES) has been designed to not touch files that don't need to be
+# changed.
+stamp-catalog-po: $(srcdir)/$(DOMAIN)-shared.pot
+	@$(CHECK_MACRO_VERSION)
+	@test ! -f $(srcdir)/$(DOMAIN).pot || { \
+	  echo "touch stamp-po" && \
+	  echo timestamp > stamp-poT && \
+	  mv stamp-poT stamp-po; \
+	}
+
+# Note: Target 'all' must not depend on target '$(DOMAIN).pot-update',
+# otherwise packages like GCC can not be built if only parts of the source
+# have been downloaded.
+
+# This target rebuilds $(DOMAIN).pot; it is an expensive operation.
+# Note that $(DOMAIN).pot is not touched if it doesn't need to be changed.
+# The determination of whether the package xyz is a GNU one is based on the
+# heuristic whether some file in the top level directory mentions "GNU xyz".
+# If GNU 'find' is available, we avoid grepping through monster files.
+$(DOMAIN)-shared.pot-update: $(POTSKELFILES:%=$(top_srcdir)/src/%) remove-potcdate.sed
+	package_gnu="$(PACKAGE_GNU)"; \
+	if test "$$package_gnu" = "yes"; then \
+	  package_prefix='GNU '; \
+	else \
+	  package_prefix=''; \
+	fi; \
+	if test -n '$(MSGID_BUGS_ADDRESS)' || test '$(PACKAGE_BUGREPORT)' = '@'PACKAGE_BUGREPORT'@'; then \
+	  msgid_bugs_address='$(MSGID_BUGS_ADDRESS)'; \
+	else \
+	  msgid_bugs_address='$(PACKAGE_BUGREPORT)'; \
+	fi; \
+	case `$(XGETTEXT) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
+	  '' | 0.[0-9] | 0.[0-9].* | 0.1[0-5] | 0.1[0-5].* | 0.16 | 0.16.[0-1]*) \
+	    $(XGETTEXT) -C --default-domain=$(DOMAIN)-shared --directory=$(top_srcdir)/src \
+	      --add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS_FOR_SKELS) @XGETTEXT_EXTRA_OPTIONS@ \
+	      --copyright-holder='' \
+	      --msgid-bugs-address="$$msgid_bugs_address" \
+              $(POTSKELFILES) \
+	    ;; \
+	  *) \
+	    $(XGETTEXT) -C --default-domain=$(DOMAIN)-shared --directory=$(top_srcdir)/src \
+	      --add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS_FOR_SKELS) @XGETTEXT_EXTRA_OPTIONS@ \
+	      --copyright-holder='' \
+	      --package-name="$${package_prefix}@PACKAGE@" \
+	      --package-version='@VERSION@' \
+	      --msgid-bugs-address="$$msgid_bugs_address" \
+              $(POTSKELFILES) \
+	    ;; \
+	esac
+	test ! -f $(DOMAIN)-shared.po || { \
+	  if test -f $(srcdir)/$(DOMAIN)-shared.pot; then \
+	    sed -f remove-potcdate.sed < $(srcdir)/$(DOMAIN)-shared.pot > $(DOMAIN)-shared.1po && \
+	    sed -f remove-potcdate.sed < $(DOMAIN)-shared.po > $(DOMAIN)-shared.2po && \
+	    if cmp $(DOMAIN)-shared.1po $(DOMAIN)-shared.2po >/dev/null 2>&1; then \
+	      rm -f $(DOMAIN)-shared.1po $(DOMAIN)-shared.2po $(DOMAIN)-shared.po; \
+	    else \
+	      rm -f $(DOMAIN)-shared.1po $(DOMAIN)-shared.2po $(srcdir)/$(DOMAIN)-shared.pot && \
+	      mv $(DOMAIN)-shared.po $(srcdir)/$(DOMAIN)-shared.pot; \
+	    fi; \
+	  else \
+	    mv $(DOMAIN)-shared.po $(srcdir)/$(DOMAIN)-shared.pot; \
+	  fi; \
+	}
+
+# This rule has no dependencies: we don't need to update $(DOMAIN).pot at
+# every "make" invocation, only create it when it is missing.
+# Only "make $(DOMAIN).pot-update" or "make dist" will force an update.
+$(srcdir)/$(DOMAIN)-shared.pot:
+	$(MAKE) $(DOMAIN)-shared.pot-update
+
+# This target rebuilds a PO file if $(DOMAIN).pot has changed.
+# Note that a PO file is not touched if it doesn't need to be changed.
+$(POSKELFILES): $(POSKELFILESDEPS)
+	@lang=`echo $@ | sed -e 's,.*/,,' -e 's/\.po$$//'`; \
+	if test -f "$(srcdir)/$${lang}.po"; then \
+	  test -f $(srcdir)/$(DOMAIN)-shared.pot || $(MAKE) $(srcdir)/$(DOMAIN)-shared.pot; \
+	  test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
+	  echo "$${cdcmd}$(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} $${lang}-shared.po $(DOMAIN)-shared.pot"; \
+	  cd $(srcdir) \
+	    && { case `$(MSGMERGE_UPDATE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
+	           '' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \
+	             $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) $${lang}-shared.po $(DOMAIN)-shared.pot;; \
+	           *) \
+	             $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} $${lang}-shared.po $(DOMAIN)-shared.pot;; \
+	         esac; \
+	       }; \
+	else \
+	  $(MAKE) $${lang}-shared.po-create; \
+	fi
+
+update-shared-po: Makefile
+	$(MAKE) $(DOMAIN)-shared.pot-update
+	test -z "$(UPDATEPOFILES)" || $(MAKE) $(UPDATEPOFILES)
