#!/usr/bin/env bash
define() { IFS='\n' read -r -d '' ${1} || true ; }
myname="${0##*/}"
define pod <<"=cut"
=encoding utf-8
=head1 NAME
xlate - translate CLI front-end for App::Greple::xlate module
=head1 SYNOPSIS
xlate [ options ] -t lang file [ greple options ]
-h help
-v show version
-d debug
-n dry-run
-a use API
-c just check translation area
-r refresh cache
-u force update cache
-s silent mode
-e # translation engine (*deepl, gpt3, gpt4, gpt4o)
-p # pattern to determine translation area
-x # file containing mask patterns
-w # wrap line by # width
-o # output format (*xtxt, cm, ifdef, space, space+, colon)
-f # from lang (ignored)
-t # to lang (required, no default)
-m # max length per API call
-l # show library files (XLATE.mk, xlate.el)
-- terminate option parsing
N.B. default is marked as *
Make options
-M run make
-n dry-run
Docker options
-G mount git top-level directory
-R mount read-only
-U do not mount
-B run in non-interactive (batch) mode
-E # specify environment variable to be inherited
-I # docker image or version (default: tecolicom/xlate:version)
# -V # specify directory to be mounted (default: current directory)
-D * run xlate on the container with the rest parameters
-C * run following command on the container, or run shell
N.B. -D/-C terminates option parsing
Control Files:
*.LANG translation languates
*.FORMAT translation foramt (xtxt, cm, ifdef, colon, space)
*.ENGINE translation engine (deepl, gpt3, gpt4, gpt4o)
# marked as # options are experimental
=head1 VERSION
Version 0.42
=head1 DESCRIPTION
B<XLATE> is a versatile command-line tool designed as a user-friendly
frontend for the B<greple> C<-Mxlate> module, simplifying the process
of multilingual automatic translation using various API services. It
streamlines the interaction with the underlying module, making it
easier for users to handle diverse translation needs across multiple
file formats and languages.
A key feature of B<xlate> is its seamless integration with Docker
environments, allowing users to quickly set up and use the tool
without complex environment configurations. This Docker support
ensures consistency across different systems and simplifies
deployment, benefiting both individual users and teams working on
translation projects.
B<xlate> supports various document formats, including C<.docx>,
C<.pptx>, and C<.md> files, and offers multiple output formats to suit
different requirements. By combining Docker capabilities with
built-in make functionality, B<xlate> enables powerful automation of
translation workflows. This combination facilitates efficient batch
processing of multiple files, streamlined project management, and easy
integration into continuous integration/continuous deployment (CI/CD)
pipelines, significantly enhancing productivity in large-scale
localization efforts.
=head2 Basic Usage
To translate a file, use the following command:
xlate -t <target_language> <file>
For example, to translate a file from English to Japanese:
xlate -t JA example.txt
=head2 Translation Engines
xlate supports multiple translation engines. Use the -e option to
specify the engine:
xlate -e deepl -t JA example.txt
Available engines: deepl, gpt3, gpt4, gpt4o
=head2 Output Formats
Various output formats are supported. Use the -o option to specify the format:
xlate -o cm -t JA example.txt
Available formats: xtxt, cm, ifdef, space, space+, colon
=head2 Docker Support
B<xlate> offers seamless integration with Docker, providing a powerful
and flexible environment for translation tasks. This approach
combines the strengths of xlate's translation capabilities with
Docker's containerization benefits.
=head3 Key Concepts
=over 4
=item B<Containerized Environment>
By running xlate in a Docker container, you ensure a consistent and
isolated environment for all translation tasks. This eliminates
issues related to system dependencies or conflicting software
versions.
=item B<Integration with Make>
The Docker functionality can be combined with xlate's B<make> feature,
allowing for complex, multi-file translation projects to be managed
efficiently within a containerized environment. For example:
xlate -DM -t 'EN FR DE' project_files/*.docx
This command runs B<xlate> in a Docker container, utilizing make to
process multiple files with specified target languages.
=item B<Flexible Mount Options>
B<xlate> provides options to mount different directories, including
Git repositories (C<-G>) and read-only mounts (C<-R>). This allows
for flexible workflow integration while maintaining data security.
=item B<Environment Variable Handling>
With the ability to pass specific environment variables into the
container (C<-E>), you can easily manage API keys and other
configuration settings without modifying the container itself.
=item B<Interactive and Batch Modes>
xlate supports both interactive (C<-C>) and non-interactive (C<-B>)
modes when running in Docker, catering to different use cases from
development to automated processing.
=back
=head2 Make Support
xlate utilizes GNU Make for automating and managing translation tasks.
This feature is particularly useful for handling translations of
multiple files or to different languages.
To use the make feature:
xlate -M [options] [target]
xlate provides a specialized Makefile (F<XLATE.mk>) that defines
translation tasks and rules. This file is located in the xlate
library directory and is automatically used when the -M option is
specified.
Example usage:
xlate -M -t 'EN FR DE' document.docx
This command will use make to translate document.docx to English,
French, and German, following the rules defined in XLATE.mk.
The C<-n> option can be used with C<-M> for a dry-run, showing what
actions would be taken without actually performing the translations:
xlate -M -n -t 'EN FR DE' document.docx
Users can customize the translation process using parameter files:
=over 4
=item F<*.LANG>:
Specifies target languages for a specific file
=item F<*.FORMAT>:
Defines output formats for a specific file
=item F<*.ENGINE>:
Selects the translation engine for a specific file
=back
For more detailed information on the make functionality and available
rules, refer to the F<XLATE.mk> file in the xlate library directory.
=head1 OPTIONS
=over 7
=item B<-h>
Show help message.
=item B<-v>
Show version information.
=item B<-d>
Enable debug mode.
=item B<-n>
Perform a dry-run without making any changes.
=item B<-a>
Use API for translation.
=item B<-c>
Check translation area without performing translation.
=item B<-r>
Refresh the translation cache.
=item B<-u>
Force update of the translation cache.
=item B<-s>
Run in silent mode.
=item B<-e> I<engine>
Specify the translation engine to use.
=item B<-p> I<pattern>
Specify a pattern to determine the translation area.
=item B<-x> I<file>
Specify a file containing mask patterns.
=item B<-w> I<width>
Wrap lines at the specified width.
=item B<-o> I<format>
Specify the output format.
=item B<-f> I<lang>
Specify the source language (currently ignored).
=item B<-t> I<lang>
Specify the target language (required).
=item B<-m> I<length>
Specify the maximum length per API call.
=item B<-l> I<file>
Show library files (XLATE.mk, xlate.el).
=back
=head2 MAKE OPTIONS
=over 7
=item B<-M>
Run make.
=item B<-n>
Dry run.
=back
=head2 DOCKER OPTIONS
Docker feature is invoked by the B<-D> or B<-C> option. These are
exclusive, so only one of them can be used. Also, once these two
options appear, subsequent options are not interpreted, so they should
always be specified last.
=over 7
=item B<-G>
Mount L<git(1)> top-level directory when running in Docker.
=item B<-R>
Mount directory as read-only in Docker.
=item B<-U>
Do not mount any directory.
=item B<-B>
Run in non-interactive (batch) mode in Docker.
=item B<-E> I<var>
Specify environment variable to be inherited in Docker.
=item B<-I> I<image>
Specify Docker image or version.
=item B<-D>
Run xlate on the Docker container with the rest of the parameters.
=item B<-C>
Run the following command on the Docker container, or run a shell if
no command is provided.
=back
=head1 ENVIRONMENT
=over 4
=item DEEPL_AUTH_KEY
Authentication key for DeepL API.
=item OPENAI_API_KEY
API key for OpenAI (used for GPT-based engines).
=back
=head1 FILES
=over 4
=item F<*.LANG>
Specifies translation languages.
=item F<*.FORMAT>
Specifies translation format.
=item F<*.ENGINE>
Specifies translation engine.
=back
=head1 EXAMPLES
1. Translate a Word document to English:
xlate -DMa -t EN-US example.docx
2. Translate to multiple languages and formats:
xlate -M -o 'xtxt ifdef' -t 'EN-US KO ZH' example.docx
3. Run a command in Docker container:
xlate -C sdif -V --nocdif example.EN-US.cm | less
4. Translate without using API (via clipboard):
xlate -t JA README.md
=head1 SEE ALSO
L<App::Greple::xlate>
=head1 AUTHOR
Kazumasa Utashiro
=head1 LICENSE
Copyright © 2023-2024 Kazumasa Utashiro.
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
my_version=$(awk '$1=="Version"{print $2; exit}' <<< "$pod")
git_topdir() {
[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" == true ] || return
local dir="$(git rev-parse --show-superproject-working-tree)"
[ "$dir" ] || dir="$(git rev-parse --show-toplevel)"
echo -n $dir
}
env_pattern() {
local name
while (( $# > 0 ))
do
name+="${name:+\\|}$1"
shift
done
echo -n ${name:+"^\\($name\\)="}
}
get_ip() {
local ip=$(ifconfig 2>/dev/null | awk '/inet /{print $2}' | tail -1)
echo -n $ip
}
repository=tecolicom/xlate
hostname=
mount=yes
autoremove=yes
interactive=yes
version=
workdir=/work
mount_mode=rw
read_only=
display=
[ -e ${localtime:=/etc/localtime} ] || localtime=
DEFAULT_ENV="$(sed 's/ */\n/g' <<-EOF
LANG TZ
LESS=-cR LESSANSIENDCHARS=mK
HTTP_PROXY HTTPS_PROXY
http_proxy https_proxy
TERM_PROGRAM TERM_BGCOLOR COLORTERM
DEEPL_AUTH_KEY
OPENAI_API_KEY
EOF
)"$'\n'
declare -a ENV
while getopts :${EXOPT:=dBUI:V:T:LRE:GCD} OPT
do
case $OPT in
d) debug=yes ; set -x ;;
B) interactive= ;;
U) mount= ;;
V) volume="$OPTARG" ;;
R) mount_mode=ro ;;
I)
if [[ "$OPTARG" =~ ^(v?[0-9.]*|latest)$ ]]
then
version=:"$OPTARG"
else
container="$OPTARG"
fi
;;
E)
DEFAULT_ENV+="$OPTARG"$'\n' ;;
G)
[ "$XLATE_RUNNING_ON_DOCKER" ] && break
if [ "${topdir:=$(git_topdir)}" ]
then
if [ "$topdir" != "${PWD:=$(pwd)}" ]
then
echo "Mount $topdir to $workdir" >&2
volume=$topdir
cd_path=${PWD#$topdir}
fi
fi
;;
C|D)
[ "$XLATE_RUNNING_ON_DOCKER" ] && break
if [ "$DISPLAY" ] && [ "${ip:=$(get_ip)}" ]
then
display="$ip:0"
fi
if [ ! "$version" ]
then
[[ "$my_version" =~ ^[0-9]+\.[0-9_]+$ ]] && version=":$my_version"
fi
declare -a command
case "$OPT" in
C) shift $((OPTIND - 1)) ;;
D) command=(xlate) ;;
esac
: ${container:=${repository}${version}}
: ${hostname:=$(echo ${container} | sed -e s:.*/:: -e s/:.*//)}
command+=(${1+"$@"})
[ ${#ENV[@]} -gt 0 ] && ENV_PATTERN="$(env_pattern ${ENV[@]})"
exec docker run \
-e XLATE_RUNNING_ON_DOCKER=1 \
${autoremove:+--rm} \
${interactive:+-it} \
${read_only:+--read-only} \
${hostname:+--hostname "${hostname}"} \
${localtime:+-v ${localtime}:/etc/localtime:ro} \
${mount:+-v "${volume:-$(pwd)}:${workdir}:${mount_mode}" \
-w "${workdir}${cd_path}"} \
${display:+-e DISPLAY="$display"} \
${DEFAULT_ENV:+--env-file <(echo "$DEFAULT_ENV")} \
${ENV_PATTERN:+--env-file <(env | grep -e "$ENV_PATTERN")} \
${container} \
"${command[@]}"
exit;
;;
esac
done
OPTIND=1
mod="App-Greple-xlate"
share=$(perl -MFile::Share=:all -E "say dist_dir '$mod'")
declare -a pattern
while getopts "${EXOPT}"'Macrusne:w:o:f:t:m:x:p:l:hv' OPT
do
case $OPT in
[$EXOPT]) : ignore ;;
M) run_make=yes ;;
a) use_api=yes ;;
c) check=yes ;;
r) refresh=yes ;;
u) update=yes ;;
s) silent=yes ;;
n) dryrun=yes ;;
e) engine="$OPTARG" ;;
w) wrap="$OPTARG" ;;
o) format="$OPTARG" ;;
f) from_lang="$OPTARG" ;;
t) to_lang="$OPTARG" ;;
m) max="$OPTARG" ;;
x) maskfile="$OPTARG" ;;
p)
pattern+=(-E "$OPTARG")
;;
l)
file="$share/$OPTARG"
if [ -f "$file" ]
then
cat $file
else
echo $share
ls -1 $share | sed 's/^/\t/'
fi
exit
;;
h)
sed -r \
-e '/^$/N' \
-e '/^\n*#/d' \
-e 's/^(\n*)=[a-z]+[0-9]* */\1/' \
-e '/Version/q' \
<<< "$pod"
exit
;;
v)
echo $my_version
exit
;;
esac
done
shift $((OPTIND - 1))
: run make
if [ "$run_make" == yes ]
then
declare -a opt
for m in "$@"
do
if [ -f "$m" ]
then
# GNU Make behaves differently in different versions with
# respect to double-quoted strings and spaces within them.
files="${files:+$files|||}$m"
else
opt+=("$m")
fi
done
unset MAKELEVEL
exec make -f $share/XLATE.mk \
${dryrun:+-n} \
XLATE_LANG=\""$to_lang"\" \
XLATE_DEBUG=$debug \
XLATE_MAXLEN=$max \
XLATE_USEAPI=$use_api \
${engine:+XLATE_ENGINE=\""$engine"\"} \
${format:+XLATE_FORMAT=\""$format"\"} \
${files:+XLATE_FILES=\""$files"\"} \
${opt[@]}
exit 1
fi
if [ ! "$to_lang" ]
then
echo "-t option is required."
exit 1
fi
: ${format:=xtxt}
: ${engine:=deepl}
if [[ "$format" =~ ^(.+)-fold$ ]]
then
format=${BASH_REMATCH[1]}
: ${wrap:=76}
fi
declare -a module option
module+=(-Mxlate)
option+=(--xlate-engine="$engine")
option+=(--xlate-to="$to_lang" --xlate-format="$format" --xlate-cache=yes)
option+=(--all)
[ "$use_api" == yes ] || use_clipboard=yes
[ "$check" == yes ] || option+=(--xlate${use_clipboard:+-labor})
[ "$wrap" ] && option+=(--xlate-fold-line --xlate-fold-width=$wrap)
[ "$debug" == yes ] && option+=(-dmo)
[ "$refresh" == yes ] && option+=(--xlate-cache=clear)
[ "$update" == yes ] && option+=(--xlate-update)
[ "$silent" == yes ] && option+=(--no-xlate-progress)
[ "$max" ] && option+=(--xlate-maxlen="$max")
[ "$maskfile" ] && option+=(--xlate-setopt "maskfile=$maskfile")
declare -a area
case $1 in
*.txt)
area=(-E '^(.+\n)+')
;;
*.md)
area=(-E '(?x) ^[-+#].*\n | ^\h+\K.*\n | ^(.+\n)+ ')
;;
*.pm|*.pod)
module+=(-Mperl)
option+=(--pod)
option+=(--exclude '^=head\d +(VERSION|AUTHOR|LICENSE|COPYRIGHT|SEE.?ALSO).*\n(?s:.*?)(?=^=|\z)')
area=(-E '^([\w\pP].+\n)+')
;;
*.doc|*.docx|*.pptx|*.xlsx)
module+=(-Mmsdoc)
;&
*.stxt)
option+=(--exclude '^\[.*\b(doc|docx|pptx|xlsx)\b.*\]\n')
area=(-E '^.+\n')
;;
*)
area=(-E '^(.+\n)+')
;;
esac
if (( ${#pattern[@]} > 0))
then
option+=("${pattern[@]}")
else
option+=("${area[@]}")
fi
exec=(greple "${module[@]}" "${option[@]}" ${1+"$@"})
if [ "$dryrun" ]
then
echo "${exec[@]}"
else
"${exec[@]}"
fi