nitc :: i18n_phase
nitc :: StringFinder
nitc :: i18n_phase $ ASuperstringExpr
A superstring literal. eg"a{x}b{y}c"
nitc :: i18n_phase $ Text
High-level abstraction for all text representationsnitc :: i18n_phase $ ASuperstringExpr
A superstring literal. eg"a{x}b{y}c"
nitc :: i18n_phase $ Text
High-level abstraction for all text representationsSerializable::inspect
to show more useful information
nitc :: modelbuilder
more_collections :: more_collections
Highly specific, but useful, collections-related classes.serialization :: serialization_core
Abstract services to serialize Nit objects to different formatsnitc :: toolcontext
Common command-line tool infrastructure than handle options and error messagescore :: union_find
union–find algorithm using an efficient disjoint-set data structurenitc :: astbuilder
Instantiation and transformation of semantic nodes in the AST of expressions and statementsnitc :: api_metrics
nitc :: nitmetrics
A program that collects various metrics on nit programs and librariesnitc :: nitrestful
Tool generating boilerplate code linking RESTful actions to Nit methodsclone
method of the astbuilder tool
# Basic support of internationalization through the generation of id-to-string tables
module i18n_phase
intrude import literal
private import annotation
private import parser_util
import astbuilder
redef class ToolContext
# Main phase of `language`
var localize_phase: Phase = new I18NPhase(self, [literal_phase])
end
private class I18NPhase
super Phase
redef fun process_annotated_node(nmodule, nat) do
if not nat.name == "i18n" then return
if not nmodule isa AModuledecl then
toolcontext.error(nmodule.location, "Error: The localized language can only be used on module declarations.")
return
end
var domain = nmodule.n_name.n_id.text
var lang: nullable String = null
if nat.n_args.length > 0 then
lang = nat.arg_as_string(toolcontext.modelbuilder)
if lang == null then return
end
var module_dir = nmodule.location.file.filename.dirname.realpath
var locale_dir = module_dir / "languages"
if not locale_dir.file_exists then locale_dir.mkdir
var amodule = nmodule.parent.as(AModule)
var vi = new StringFinder(domain, locale_dir, toolcontext, amodule)
vi.enter_visit(amodule)
var module_name = nmodule.location.file.filename.basename(".nit")
var pot_path = locale_dir / module_name
var arr = vi.strings.values.to_a
var po = new POFile(arr)
po.write_template(pot_path)
if lang != null then
for i in po.strings do
i.msgstr = i.msgid
end
var lang_dir = locale_dir / lang
if not lang_dir.file_exists then lang_dir.mkdir
var messages_dir = lang_dir / "LC_MESSAGES"
if not messages_dir.file_exists then messages_dir.mkdir
po.write_to_file(messages_dir / module_name)
end
var lit = new LiteralVisitor(toolcontext)
lit.enter_visit(amodule)
end
end
private class StringFinder
super Visitor
# Strings in the file, used to generate .pot and .po files
var strings = new HashMap[String, PObject]
# Domain of the strings to internationalize
var domain: String
# Location of the languages file
var languages_location: String
# Context for the visitor, used only for the parse_expr
var toolcontext: ToolContext
# The module we are working on
var amodule: AModule
redef fun visit(n)
do
n.accept_string_finder(self)
n.visit_all(self)
end
redef fun enter_visit(e) do
if e isa AAnnotation then return
super
end
# Adds a String to the list of strings of the module
#
# The string needs to be pre-formatted to C standards (escape_to_c)
fun add_string(s: String, loc: Location) do
var locstr = "{amodule.mmodule.mgroup.name}::{amodule.mmodule.name} {loc.line_start}--{loc.column_start}:{loc.column_end}"
if not strings.has_key(s) then
var po = new PObject([locstr], s, "")
strings[s] = po
else
strings[s].locations.push locstr
end
end
end
redef class ANode
private fun accept_string_finder(v: StringFinder) do end
end
redef class AStringExpr
redef fun accept_string_finder(v) do
var str = value.escape_to_gettext
var code = "\"{str}\".get_translation(\"{v.domain}\", \"{v.languages_location}\")"
var parse = v.toolcontext.parse_expr(code)
replace_with(parse)
v.add_string(str, location)
end
end
redef class ASuperstringExpr
redef fun accept_string_finder(v) do
var fmt = ""
var exprs = new Array[AExpr]
for i in n_exprs do
if i isa AStartStringExpr or i isa AEndStringExpr or i isa AMidStringExpr then
assert i isa AStringFormExpr
var str = i.value
fmt += str.replace("%", "%%")
else
fmt += "%"
exprs.push i
fmt += (exprs.length-1).to_s
end
end
fmt = fmt.escape_to_gettext
v.add_string(fmt, location)
var code = "\"{fmt}\".get_translation(\"{v.domain}\", \"{v.languages_location}\").format()"
var parse = v.toolcontext.parse_expr(code)
if not parse isa ACallExpr then
v.toolcontext.error(location, "Fatal error in i18n annotation, the parsed superstring could not be generated properly")
return
end
var parse_exprs = parse.n_args.n_exprs
parse_exprs.add_all exprs
replace_with parse
end
end
# .po file entry
#
# Locations are optional, they just serve for translation purposes
# to help the translator with the context of the message if necessary
#
# msgid and msgstr are the map of translate to translated strings in the po file.
class PObject
# Array since the same string can be encountered at several places
var locations: Array[String]
# Identifier of the string to translate (i.e. the string itself)
var msgid: String is writable
# Translation of the string
var msgstr: String is writable
end
# A GNU gettext .po/.pot file
class POFile
super Writable
# Map of the strings's `msgid` and `msgstr`
#
# Read from a PO file
var strings: Array[PObject]
redef fun write_to_file(path) do
if not path.has_suffix(".po") then path += ".po"
super path
end
redef fun write_to(ofs) do
for i in strings do
ofs.write("#: {i.locations.join(", ")}\n")
ofs.write("msgid \"{i.msgid}\"\n")
ofs.write("msgstr \"{i.msgstr}\"\n\n")
end
ofs.write("# Generated file, do not modify\n")
end
# Writes the information of the POFile to a .pot template file
fun write_template(path: String) do
if not path.has_suffix(".pot") then path += ".pot"
var f = new FileWriter.open(path)
write_to(f)
f.close
end
end
redef class Text
private fun escape_to_gettext: String
do
return escape_to_c.replace("\{", "\\\{").replace("\}", "\\\}")
end
end
src/frontend/i18n_phase.nit:15,1--223,3