From 4c2ad2bdf1765456198ff5919b6e015894642a78 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 7 Apr 2011 01:28:54 +0300 Subject: [PATCH 1/6] Reading, merging, and writing translations --- bin/poupdate | 368 ++++++++++++++++ pokedex/db/translations.py | 659 +++++++++++++++++++++++++++++ pokedex/tests/test_translations.py | 183 ++++++++ 3 files changed, 1210 insertions(+) create mode 100755 bin/poupdate create mode 100755 pokedex/db/translations.py create mode 100644 pokedex/tests/test_translations.py diff --git a/bin/poupdate b/bin/poupdate new file mode 100755 index 0000000..06ccdf2 --- /dev/null +++ b/bin/poupdate @@ -0,0 +1,368 @@ +#! /usr/bin/env python +# Encoding: UTF-8 + +u"""Creation and loading of GNU Gettext language files. + +poupdate [options] [file1.csv file2.csv ...] + +Use this script to +- Create .pot files (in pokedex/i18n/) +- Update the .po files (in pokedex/i18n/) +- Update the pokedex .csv files in (pokedex/data/csv/translations) + +To make pos for a new language, make sure it is in the database, make +a directory for it in pokedex/i18n/, and run this. + +You can also give one or more translation CSVs as arguments. +These are in the same format as veekun's main database CSVs, for example +pokedex/data/csv/ability_prose.csv. Be sure to set the correct language +ID (which implies the language must be in the database). +Also be sure to have the correct column order: first an appropriately named +foreign key, then local_language_id, and then the text columns. + +""" + +# Everything related to Gettext files, and the CLI interface, is here. +# General message handling and CSV I/O is in the pokedex library. + +# Notes on how we use PO format: +# The source information is stored in the occurences fields, using +# "table_name.column_name" for file and object ID for line number. This is used +# as a message key, instead of the source string. So it's important not to +# discard location information. It also means "obsolete" and "fuzzy" mean +# pretty much the same in our context. +# +# Also note that a pot file is just a po file with all strings untranslated. +# So some functions here will work on either. +# +# Gettext context (msgctxt) is written to the files so that tools don't merge +# unrelated strings together. It is ignored when reading the PO files. + +# Also of note, "polib" means "(do) kiss!" in Czech. + +import os +import re +import sys +from datetime import datetime +from optparse import OptionParser +from collections import defaultdict + +import pkg_resources + +from pokedex.db import tables, translations +from pokedex.defaults import get_default_csv_dir + +try: + import polib +except ImportError: + if __name__ == '__main__': + exit('This utility needs polib installed.\n$ pip install polib') + raise + +number_replacement_flag = '-pokedex-number-replacement' + +default_gettext_directory = pkg_resources.resource_filename('pokedex', 'i18n') + +mapped_class_dict = dict((c.__name__, c) for c in tables.mapped_classes) +for cls in tables.mapped_classes: + mapped_class_dict.update(dict((c.__name__, cls) for c in cls.translation_classes)) + +class PokedexPot(polib.POFile): + def __init__(self, name): + super(PokedexPot, self).__init__() + self.metadata = { + 'Project-Id-Version': 'pokedex-%s 0.1' % name, + 'Report-Msgid-Bugs-To': 'encukou@gmail.com', + 'POT-Creation-Date': datetime.now().isoformat(), + 'PO-Revision-Date': 'YEAR-MO-DA HO:MI+ZONE', + 'MIME-Version': '1.0', + 'Content-Type': 'text/plain; charset=utf-8', + 'Content-Transfer-Encoding': '8bit', + 'Generated-By': "The pokedex", + } + self.seen_entries = {} + + def append(self, entry): + """Append an entry. POEntries that only differ in numbers are merged. + + For example "Route 1", "Route 2", etc. are replaced by a single + "Route {num}". + + Multiple numbers might be replaced, for example in "{num}--{num} + different Unown caught" + + Entries without numbers are merged as well (e.g. "Has no overworld + effect" appears quite a few times in in AbilityChangelog) + """ + replaced = translations.number_re.sub('{num}', entry.msgid) + try: + common_entry = self.seen_entries[(entry.msgctxt, replaced)] + except KeyError: + self.seen_entries[(entry.msgctxt, replaced)] = entry + else: + common_entry.occurrences += entry.occurrences + # Only now is the actual entry replaced. So we get + # "Route {num}", but "Porygon2" because there's no Porygon3. + common_entry.msgid = replaced + common_entry.msgstr = translations.number_re.sub('{num}', common_entry.msgstr) + if replaced != entry.msgid and number_replacement_flag not in common_entry.flags: + common_entry.flags.append(number_replacement_flag) + return + self += [entry] + +class PotDict(dict): + """A defaultdict of pot files""" + def __missing__(self, name): + pot = PokedexPot(name) + self[name] = pot + return pot + +def yield_po_messages(pos): + """Yield messages from all given .po files + """ + merger = translations.Merge() + for po in pos.values(): + merger.add_iterator(_yield_one_po_messages(po, merger)) + return merger + +def entry_sort_key(entry): + try: + cls_col, line = entry.occurrences[0] + except IndexError: + return + else: + if line: + classname, col = cls_col.split('.') + fuzzy = entry.obsolete or 'fuzzy' in entry.flags + try: + cls = mapped_class_dict[classname] + except KeyError, k: + # Renamed table? + print 'Warning: Unknown class %s' % classname + return '', int(line), col, fuzzy + else: + return cls.__name__, int(line), col, fuzzy + +def _yield_one_po_messages(pofile, merger): + # Yield messages from one po file + # + # Messages in our po files are ordered by the first occurrence. + # The occurrences of a single message are also ordered. + # So just merge all the subsequences as we go + for entry in sorted(pofile, key=entry_sort_key): + if entry.msgstr: + fuzzy = (entry.obsolete or 'fuzzy' in entry.flags) + messages = [] + for occurrence in entry.occurrences: + cls_colname, id = occurrence + if id: + clsname, colname = cls_colname.split('.') + cls = mapped_class_dict[clsname] + messages.append(translations.Message( + mapped_class_dict[clsname].__name__, + int(id), + colname, + entry.msgstr, + source=entry.msgid, + number_replacement=number_replacement_flag in entry.flags, + origin='PO file', + fuzzy=fuzzy, + )) + if messages[1:]: + # Spawn extra iterators before yielding + merger.add_iterator(messages[1:]) + if messages: + yield messages[0] + +def create_pots(source, *translation_streams): + """Convert an iterator of Messages to a dictionary of pot/po files + + If translations are given, they're merged, and any exact matches are put + in the po file. Give some for po files, don't give any for pot files. + """ + obsolete = [] + pots = PotDict() + merged = translations.merge_translations(source, *translation_streams, unused=obsolete.append) + for source, sourcehash, string, exact in merged: + ctxt = '.'.join((source.cls, source.colname)) + entry = polib.POEntry( + msgid=source.string, + occurrences=[(ctxt, source.id)], + msgctxt=ctxt, + ) + if string: + entry.msgstr = string + if not exact: + entry.flags.append('fuzzy') + pots[source.pot].append(entry) + for message in obsolete: + ctxt = '.'.join((message.cls, message.colname)) + entry = polib.POEntry( + msgid=message.source or '???', + occurrences=[(ctxt, message.id)], + msgctxt=ctxt, + obsolete=True, + ) + return pots + +def save_pots(pots, gettext_directory=default_gettext_directory): + """Save pot files to a directory.""" + for name, pot in pots.items(): + pot.save(os.path.join(gettext_directory, 'pokedex-%s.pot' % name)) + +def save_pos(pos, lang, gettext_directory=default_gettext_directory): + """Save po files to the appropriate directory.""" + for name, po in pos.items(): + po.save(os.path.join(gettext_directory, lang, 'pokedex-%s.po' % name)) + +def read_pots(directory=default_gettext_directory, extension='.pot'): + """Read all files from the given directory with the given extension as pofiles + + Works on pos or pots. + """ + pots = {} + for filename in os.listdir(directory): + basename, ext = os.path.splitext(filename) + if ext == extension: + pots[basename] = polib.pofile(os.path.join(directory, filename)) + + return pots + +def all_langs(gettext_directory=default_gettext_directory): + return [ + d for d in os.listdir(gettext_directory) + if os.path.isdir(os.path.join(gettext_directory, d)) + ] + +def merge_pos(transl, lang, language_directory): + """Update all po files for the given language + + Takes into account the source, the official translations from the database, + the existing PO files, and the current translation CSV, in that order. + + Returns a name -> pofile dict + """ + return create_pots( + transl.source, + transl.official_messages(lang), + yield_po_messages(pos=read_pots(language_directory, '.po')), + transl.yield_target_messages(lang), + ) + +def bar(fraction, size, done_char='=', split_char='|', notdone_char='-'): + """Build an ASCII art progress bar + """ + size -= 1 + if fraction == 1: + split_char = done_char + completed = int(round(size * fraction)) + bar = [done_char] * completed + bar.append(split_char) + bar += notdone_char * (size - completed) + return ''.join(bar) + +def print_stats(pos): + """Print out some fun stats about a set of po files + """ + template = u"{0:>10}: {1:4}/{2:4} {3:6.2f}% [{4}]" + total_translated = 0 + total = 0 + for name, po in pos.items(): + num_translated = len(po.translated_entries()) + total_translated += num_translated + fraction_translated = 1. * num_translated / len(po) + total += len(po) + print template.format( + name, + num_translated, + len(po), + 100 * fraction_translated, + bar(fraction_translated, 47), + ).encode('utf-8') + fraction_translated = 1. * total_translated / total + print template.format( + 'Total', + total_translated, + total, + 100 * fraction_translated, + bar(fraction_translated, 47), + ).encode('utf-8') + + +if __name__ == '__main__': + parser = OptionParser(__doc__) + + parser.add_option('-l', '--langs', dest='langs', + help="List of languages to handle, separated by commas (example: -l 'en,de,ja') (default: all in gettext directory)") + parser.add_option('-P', '--no-pots', dest='pots', action='store_false', default=True, + help='Do not create POT files (templates)') + parser.add_option('-p', '--no-pos', dest='pos', action='store_false', default=True, + help='Do not update PO files (message catalogs)') + + parser.add_option('-c', '--no-csv', dest='csv', action='store_false', default=True, + help='Do not update pokedex translations files') + + parser.add_option('-d', '--directory', dest='directory', + help='Veekun data directory') + parser.add_option('-L', '--source-language', dest='source_lang', + help="Source language identifier (default: 'en')") + + parser.add_option('-g', '--gettext-dir', dest='gettext_directory', default=default_gettext_directory, + help='Gettext directory (default: pokedex/i18n/)') + + parser.add_option('-q', '--quiet', dest='verbose', default=True, action='store_false', + help="Don't print what's going on") + + options, arguments = parser.parse_args() + + transl = translations.Translations.from_parsed_options(options) + + gettext_directory = options.gettext_directory + if options.pots: + if options.verbose: + print 'Creating pots in', gettext_directory + save_pots(create_pots(transl.source), gettext_directory=gettext_directory) + + if options.pos or options.csv: + # Merge in CSV files from command line + csv_streams = defaultdict(translations.Merge) + for argument in arguments: + # Add each message in its own stream, to sort them. + file = open(argument, 'rb') + with file: + for message in translations.yield_guessed_csv_messages(file): + lang = transl.language_identifiers[message.language_id] + csv_streams[lang].add_iterator([message]) + streams = defaultdict(list) + for lang, stream in csv_streams.items(): + streams[lang].append(stream) + + # Merge in the PO files + if options.langs: + langs = options.langs.split(',') + else: + langs = all_langs(gettext_directory) + + for lang in langs: + language_directory = os.path.join(gettext_directory, lang) + if options.verbose: + print 'Merging translations for %s in %s' % (lang, language_directory) + pos = merge_pos(transl, lang, language_directory) + + if options.pos: + if options.verbose: + print 'Writing POs for %s' % lang + save_pos(pos, lang, gettext_directory=gettext_directory) + + if options.verbose: + print_stats(pos) + + streams[lang].append(yield_po_messages(pos)) + + if options.csv: + for lang, lang_streams in streams.items(): + if options.verbose: + print "Merging %s translation stream/s for '%s'" % (len(lang_streams), lang) + existing_messages = list(transl.yield_target_messages(lang)) + lang_streams.append(existing_messages) + transl.write_translations(lang, *lang_streams) diff --git a/pokedex/db/translations.py b/pokedex/db/translations.py new file mode 100755 index 0000000..5d1bda6 --- /dev/null +++ b/pokedex/db/translations.py @@ -0,0 +1,659 @@ +#! /usr/bin/env python +u"""General handling of translations + +The general idea is to get messages from somewhere: the source pokedex CSVs, +or the translation CSVs, etc., then merge them together in some way, and shove +them into the database. + +If a message is translated, it has a source string attached to it, with the +original English version. Or at least it has a CRC of the original. +When that doesn't match, it means the English string changed and the +translation has to be updated. +Also this is why we can't dump translations from the database: there's no +original string info. + +Some complications: + +Flavor text is so repetitive that we take strings from all the version, +separate the unique ones by blank lines, let translators work on that, and then +put it in flavor_summary tables. + +Routes names and other repetitive numeric things are replaced by e.g. +"Route {num}" so translators only have to work on each set once. + +""" + +import binascii +import csv +import heapq +import itertools +import os +import re +import sys +from collections import defaultdict + +from pokedex.db import tables +from pokedex.defaults import get_default_csv_dir + +default_source_lang = 'en' + +# Top-level classes we want translations for: in order, and by name +# These are all mapped_classes that have translatable texts and aren't summarized +toplevel_classes = [] +toplevel_class_by_name = {} + +# summary_map[pokemon_prose]['flavor_summary'] == PokemonFlavorTexts +summary_map = {} + +# translation_class_by_column[class_name, column_name] == translation_class +translation_class_by_column = {} + +for cls in tables.mapped_classes: + try: + summary_class, col = cls.summary_column + except AttributeError: + if cls.translation_classes: + toplevel_classes.append(cls) + toplevel_class_by_name[cls.__name__] = cls + for translation_class in cls.translation_classes: + for column in translation_class.__table__.c: + translation_class_by_column[cls, column.name] = translation_class + else: + summary_map.setdefault(summary_class, {})[col] = cls + +number_re = re.compile("[0-9]+") + +def crc(string): + """Return a hash to we use in translation CSV files""" + return "%08x" % (binascii.crc32(string.encode('utf-8')) & 0xffffffff) + # Two special values are also used in source_crc: + # UNKNOWN: no source string was available + # OFFICIAL: an official string from the main database + +class Message(object): + """Holds all info about a translatable or translated string + + cls: Name of the mapped class the message belongs to + id: The id of the thing the message belongs to + colname: name of the database column + strings: A list of strings in the message, usualy of length 1. + + Optional attributes (None if not set): + colsize: Max length of the database column + source: The string this was translated from + number_replacement: True if this is a translation with {num} placeholders + pot: Name of the pot the message goes to (see pot_for_column) + source_crc: CRC of the source + origin: Some indication of where the string came from (CSV, PO, ...) + fuzzy: True for fuzzy translations + language_id: ID of the language + official: True if this is a known-good translation + """ + __slots__ = 'cls id colname strings colsize source number_replacement pot source_crc origin fuzzy language_id official'.split() + def __init__(self, cls, id, colname, string, + colsize=None, source=None, number_replacement=None, pot=None, + source_crc=None, origin=None, fuzzy=None, language_id=None, + official=None, + ): + self.cls = cls + self.id = id + self.colname = colname + self.strings = [string] + self.colsize = colsize + self.source = source + self.number_replacement = number_replacement + self.pot = pot + self.source_crc = source_crc + if source and not source_crc: + self.source_crc = crc(source) + self.origin = origin + self.fuzzy = fuzzy + self.language_id = language_id + self.official = official + + def merge(self, other): + """Merge two messages, as required for flavor text summarizing + """ + assert self.merge_key == other.merge_key + for string in other.strings: + if string not in self.strings: + self.strings.append(string) + self.colsize = self.colsize or other.colsize + self.pot = self.pot or other.pot + self.source = None + self.source_crc = None + self.number_replacement = None + + @property + def string(self): + return '\n\n'.join(self.strings) + + @property + def merge_key(self): + return self.cls, self.id, self.colname + + @property + def sort_key(self): + return self.merge_key, self.language_id, self.fuzzy + + @property + def eq_key(self): + return self.sort_key, self.strings + + def __eq__(self, other): return self.eq_key == other.eq_key + def __ne__(self, other): return self.eq_key != other.eq_key + def __gt__(self, other): return self.sort_key > other.sort_key + def __lt__(self, other): return self.sort_key < other.sort_key + def __ge__(self, other): return self.sort_key >= other.sort_key + def __le__(self, other): return self.sort_key <= other.sort_key + + def __unicode__(self): + string = '"%s"' % self.string + if len(string) > 20: + string = string[:15] + u'"...' + template = u'' + return template.format(self=self, string=string) + + def __str__(self): + return unicode(self).encode('utf-8') + + def __repr__(self): + return unicode(self).encode('utf-8') + +class Translations(object): + """Data and opertaions specific to a location on disk (and a source language) + """ + def __init__(self, source_lang=default_source_lang, csv_directory=None, translation_directory=None): + if csv_directory is None: + csv_directory = get_default_csv_dir() + + if translation_directory is None: + translation_directory = os.path.join(csv_directory, 'translations') + + self.source_lang = default_source_lang + self.csv_directory = csv_directory + self.translation_directory = translation_directory + + self.language_ids = {} + self.language_identifiers = {} + self.official_langs = [] + for row in self.reader_for_class(tables.Language, reader_class=csv.DictReader): + self.language_ids[row['identifier']] = int(row['id']) + self.language_identifiers[int(row['id'])] = row['identifier'] + if row['official'] and int(row['official']): + self.official_langs.append(row['identifier']) + + self.source_lang_id = self.language_ids[self.source_lang] + + @classmethod + def from_parsed_options(cls, options): + return cls(options.source_lang, options.directory) + + @property + def source(self): + """All source (i.e. English) messages + """ + return self.official_messages(self.source_lang) + + def official_messages(self, lang): + """All official messages (i.e. from main database) for the given lang + """ + # Cached as tuples, since they're used pretty often + lang_id = self.language_ids[lang] + try: + return self._sources[lang_id] + except AttributeError: + self._sources = {} + for message in self.yield_source_messages(): + self._sources.setdefault(message.language_id, []).append(message) + self._sources = dict((k, tuple(merge_adjacent(v))) for k, v in self._sources.items()) + return self.official_messages(lang) + except KeyError: + # Looks like there are no messages in the DB for this language + # This should only happen for non-official languages + assert lang not in self.official_langs + return () + + def write_translations(self, lang, *streams): + """Write a translation CSV containing messages from streams. + + Streams should be ordered by priority, from highest to lowest. + + Any official translations (from the main database) are added automatically. + """ + writer = self.writer_for_lang(lang) + + writer.writerow('language_id table id column source_crc string'.split()) + + messages = merge_translations(self.source, self.official_messages(lang), *streams) + + warnings = {} + for source, sourcehash, string, exact in messages: + if string and sourcehash != 'OFFICIAL': + utf8len = len(string.encode('utf-8')) + if source.colsize and utf8len > source.colsize: + key = source.cls, source.colname + warnings[key] = max(warnings.get(key, (0,)), (utf8len, source, string)) + else: + writer.writerow(( + self.language_ids[lang], + source.cls, + source.id, + source.colname, + sourcehash, + string.encode('utf-8'), + )) + for utf8len, source, string in warnings.values(): + template = u'Error: {size}B value for {colsize}B column! {key[0]}.{key[2]}:{key[1]}: {string}' + warning = template.format( + key=source.merge_key, + string=string, + size=utf8len, + colsize=source.colsize, + ) + if len(warning) > 79: + warning = warning[:76] + u'...' + print warning.encode('utf-8') + + def reader_for_class(self, cls, reader_class=csv.reader): + tablename = cls.__table__.name + csvpath = os.path.join(self.csv_directory, tablename + '.csv') + return reader_class(open(csvpath, 'rb'), lineterminator='\n') + + def writer_for_lang(self, lang): + csvpath = os.path.join(self.translation_directory, '%s.csv' % lang) + return csv.writer(open(csvpath, 'wb'), lineterminator='\n') + + def yield_source_messages(self, language_id=None): + """Yield all messages from source CSV files + + Messages from all languages are returned. The messages are not ordered + properly, but splitting the stream by language (and filtering results + by merge_adjacent) will produce proper streams. + """ + if language_id is None: + language_id = self.source_lang_id + + for cls in sorted(toplevel_classes, key=lambda c: c.__name__): + streams = [] + for translation_class in cls.translation_classes: + streams.append(yield_source_csv_messages( + translation_class, + cls, + self.reader_for_class(translation_class), + )) + try: + colmap = summary_map[translation_class] + except KeyError: + pass + else: + for colname, summary_class in colmap.items(): + column = translation_class.__table__.c[colname] + streams.append(yield_source_csv_messages( + summary_class, + cls, + self.reader_for_class(summary_class), + force_column=column, + )) + for message in Merge(*streams): + yield message + + def yield_target_messages(self, lang): + """Yield messages from the data/csv/translations/.csv file + """ + path = os.path.join(self.csv_directory, 'translations', '%s.csv' % lang) + try: + file = open(path, 'rb') + except IOError: + return () + return yield_translation_csv_messages(file) + + def yield_all_translations(self): + stream = Merge() + for lang in self.language_identifiers.values(): + stream.add_iterator(self.yield_target_messages(lang)) + return (message for message in stream if not message.official) + + def get_load_data(self, langs=None): + """Yield (translation_class, data for INSERT) pairs for loading into the DB + + langs is either a list of language identifiers or None + """ + if langs is None: + langs = self.language_identifiers.values() + stream = Merge() + for lang in self.language_identifiers.values(): + stream.add_iterator(self.yield_target_messages(lang)) + stream = (message for message in stream if not message.official) + count = 0 + class GroupDict(dict): + """Dict to automatically set the foreign_id and local_language_id for new items + """ + def __missing__(self, key): + # depends on `cls` from outside scope + id, language_id = key + data = self[key] = defaultdict(lambda: None) + column_names = (c.name for c in translation_class.__table__.columns) + data.update(dict.fromkeys(column_names)) + data.update({ + '%s_id' % cls.__singlename__: id, + 'local_language_id': language_id, + }) + return data + # Nested dict: + # translation_class -> (lang, id) -> column -> value + everything = defaultdict(GroupDict) + # Group by object so we always have all of the messages for one DB row + for (cls_name, id), group in group_by_object(stream): + cls = toplevel_class_by_name[cls_name] + for message in group: + translation_class = translation_class_by_column[cls, message.colname] + key = id, message.language_id + colname = str(message.colname) + everything[translation_class][key][colname] = message.string + count += 1 + if count > 1000: + for translation_class, key_data in everything.items(): + yield translation_class, key_data.values() + count = 0 + everything.clear() + for translation_class, data_dict in everything.items(): + yield translation_class, data_dict.values() + +def group_by_object(stream): + """Group stream by object + + Yields ((class name, object ID), (list of messages)) pairs. + """ + stream = iter(stream) + current = stream.next() + current_key = current.cls, current.id + group = [current] + for message in stream: + if (message.cls, message.id) != current_key: + yield current_key, group + group = [] + group.append(message) + current = message + current_key = current.cls, current.id + yield current_key, group + +class Merge(object): + """Merge several sorted iterators together + + Additional iterators may be added at any time with add_iterator. + Accepts None for the initial iterators + If the same value appears in more iterators, there will be duplicates in + the output. + """ + def __init__(self, *iterators): + self.next_values = [] + for iterator in iterators: + if iterator is not None: + self.add_iterator(iterator) + + def add_iterator(self, iterator): + iterator = iter(iterator) + try: + value = iterator.next() + except StopIteration: + return + else: + heapq.heappush(self.next_values, (value, iterator)) + + def __iter__(self): + return self + + def next(self): + if self.next_values: + value, iterator = heapq.heappop(self.next_values) + self.add_iterator(iterator) + return value + else: + raise StopIteration + +def merge_adjacent(gen): + """Merge adjacent messages that compare equal""" + gen = iter(gen) + last = gen.next() + for this in gen: + if this.merge_key == last.merge_key: + last.merge(this) + elif last < this: + yield last + last = this + else: + raise AssertionError('Bad order, %s > %s' % (last, this)) + yield last + +def leftjoin(left_stream, right_stream, key=lambda x: x, unused=None): + """A "left join" operation on sorted iterators + + Yields (left, right) pairs, where left comes from left_stream and right + is the corresponding item from right, or None + + Note that if there are duplicates in right_stream, you won't get duplicate + rows for them. + + If given, unused should be a one-arg function that will get called on all + unused items in right_stream. + """ + left_stream = iter(left_stream) + right_stream = iter(right_stream) + try: + right = right_stream.next() + for left in left_stream: + while right and key(left) > key(right): + if unused is not None: + unused(right) + right = right_stream.next() + if key(left) == key(right): + yield left, right + del left + right = right_stream.next() + else: + yield left, None + except StopIteration: + try: + yield left, None + except NameError: + pass + for left in left_stream: + yield left, None + else: + if unused is not None: + try: + unused(right) + except NameError: + pass + for right in right_stream: + unused(right) + +def synchronize(reference, stream, key=lambda x: x, unused=None): + """Just the right side part of leftjoin(), Nones included""" + for left, right in leftjoin(reference, stream, key, unused): + yield right + +def yield_source_csv_messages(cls, foreign_cls, csvreader, force_column=None): + """Yield all messages from one source CSV file. + """ + columns = list(cls.__table__.c) + column_names = csvreader.next() + # Assumptions: rows are in lexicographic order + # (taking numeric values as numbers of course) + # Assumptions about the order of columns: + # 1. It's the same in the table and in CSV + # 2. Primary key is at the beginning + # 3. First thing in the PK is the object id + # 4. Last thing in the PK is the language + # 5. Everything that follows is some translatable text + assert [cls.__table__.c[name] for name in column_names] == columns, ','.join(c.name for c in columns) + pk = columns[:len(cls.__table__.primary_key.columns)] + first_string_index = len(pk) + return _yield_csv_messages(foreign_cls, columns, first_string_index, csvreader, force_column=force_column) + +def _yield_csv_messages(foreign_cls, columns, first_string_index, csvreader, origin='source CSV', crc_value='OFFICIAL', force_column=None): + language_index = first_string_index - 1 + assert 'language' in columns[language_index].name, columns[language_index].name + string_columns = columns[first_string_index:] + if force_column is not None: + assert len(string_columns) == 1 + string_columns = [force_column] + for values in csvreader: + id = int(values[0]) + messages = [] + for string, column in zip(values[first_string_index:], string_columns): + message = Message( + foreign_cls.__name__, + id, + column.name, + string.decode('utf-8'), + column.type.length, + pot=pot_for_column(cls, column, force_column is not None), + origin=origin, + official=True, + source_crc=crc_value, + language_id=int(values[language_index]), + ) + messages.append(message) + messages.sort() + for message in messages: + yield message + +def yield_guessed_csv_messages(file): + """Yield messages from a CSV file, using the header to figure out what the data means. + """ + csvreader = csv.reader(file, lineterminator='\n') + column_names = csvreader.next() + if column_names == 'language_id,table,id,column,source_crc,string'.split(','): + # A translation CSV + return yield_translation_csv_messages(file, True) + # Not a translation CSV, figure out what the columns mean + assert column_names[0].endswith('_id') + assert column_names[1] == 'local_language_id' + first_string_index = 2 + foreign_singlename = column_names[0][:-len('_id')] + columns = [None] * len(column_names) + column_indexes = dict((name, i) for i, name in enumerate(column_names)) + for foreign_cls in toplevel_classes: + if foreign_cls.__singlename__ == foreign_singlename: + break + else: + raise ValueError("Foreign key column name %s in %s doesn't correspond to a table" % (column_names[0], file)) + for translation_class in foreign_cls.translation_classes: + for column in translation_class.__table__.c: + column_index = column_indexes.get(column.name) + if column_index is not None: + columns[column_index] = column + assert all([c is not None for c in columns[first_string_index:]]) + return _yield_csv_messages(foreign_cls, columns, first_string_index, csvreader, origin=file.name, crc_value='UNKNOWN') + +def yield_translation_csv_messages(file, no_header=False): + """Yield messages from a translation CSV file + """ + csvreader = csv.reader(file, lineterminator='\n') + if not no_header: + columns = csvreader.next() + assert columns == 'language_id,table,id,column,source_crc,string'.split(',') + for language_id, table, id, column, source_crc, string in csvreader: + yield Message( + table, + int(id), + column, + string.decode('utf-8'), + origin='target CSV', + source_crc=source_crc, + language_id=int(language_id), + ) + +def pot_for_column(cls, column, summary=False): + """Translatable texts get categorized into different POT files to help + translators prioritize. The pots are: + + - flavor: Flavor texts: here, strings from multiple versions are summarized + - ripped: Strings ripped from the games; translators for "official" + languages don't need to bother with these + - effects: Fanon descriptions of things; they usually use technical + language + - misc: Everything else; usually small texts + + Set source to true if this is a flavor summary column. Others are + determined by the column itself. + """ + if summary: + return 'flavor' + elif column.info.get('ripped'): + return 'ripped' + elif column.name.endswith('effect'): + return 'effects' + else: + return 'misc' + +def number_replace(source, string): + numbers_iter = iter(number_re.findall(source)) + next_number = lambda match: numbers_iter.next() + return re.sub(r'\{num\}', next_number, string) + +def match_to_source(source, *translations): + """Matches translated string(s) to source + + The first translation whose source matches the source message, or whose CRC + matches, or which is official, and which is not fuzzy, it is used. + If thre's no such translation, the first translation is used. + + Returns (source, source string CRC, string for CSV file, exact match?) + If there are no translations, returns (source, None, None, None) + + Handles translations where numbers have been replaced by {num}, if they + have source information. + """ + first = True + best_crc = None + for translation in translations: + if translation is None: + continue + if translation.number_replacement: + current_string = number_replace(source.string, translation.string) + current_source = number_replace(source.string, translation.source) + current_crc = crc(current_source) + elif '{num}' in translation.string: + print (u'Warning: {num} appears in %s, but not marked for number replacement. Discarding!' % translation).encode('utf-8') + continue + else: + current_string = translation.string + current_source = translation.source + current_crc = translation.source_crc + if translation.fuzzy: + match = False + elif translation.official: + match = True + elif current_source: + match = source.string == current_source + else: + match = current_crc == crc(source.string) + if first or match: + best_string = current_string + best_crc = current_crc + best_message = translation + if match: + break + first = False + if best_crc: + return source, best_crc, best_string, match + else: + return source, None, None, None + +def merge_translations(source_stream, *translation_streams, **kwargs): + """For each source message, get its best translation from translations. + + Translations should be ordered by priority, highest to lowest. + + Messages that don't appear in translations at all aren't included. + """ + source = tuple(source_stream) + streams = [ + synchronize(source, t, key=lambda m: m.merge_key, unused=kwargs.get('unused')) + for t in translation_streams + ] + for messages in itertools.izip(source, *streams): + yield match_to_source(*messages) diff --git a/pokedex/tests/test_translations.py b/pokedex/tests/test_translations.py new file mode 100644 index 0000000..af96331 --- /dev/null +++ b/pokedex/tests/test_translations.py @@ -0,0 +1,183 @@ +# Encoding: UTF-8 + +import csv + +from nose.tools import * + +from pokedex.db import translations, tables + +fake_version_names = ( + 'version_id,local_language_id,name', + '1,0,name1', '2,0,name2', '3,0,name3', '3,1,othername3', + ) + +fake_translation_csv = ( + 'language_id,table,id,column,source_crc,string', + '0,Version,1,name,,name1', + '0,Version,2,name,,name2', + '0,Version,3,name,,name3', + '1,Version,3,name,,othername3', + ) + +def test_yield_source_csv_messages(): + check_version_message_stream(translations.yield_source_csv_messages( + tables.Version.names_table, + tables.Version, + csv.reader(iter(fake_version_names)), + )) + +def test_yield_guessed_csv_messages(): + check_version_message_stream(translations.yield_guessed_csv_messages( + iter(fake_translation_csv), + )) + +def test_yield_translation_csv_messages(): + check_version_message_stream(translations.yield_translation_csv_messages( + iter(fake_translation_csv), + )) + +def check_version_message_stream(messages): + messages = list(messages) + assert messages[0].string == 'name1' + assert messages[1].string == 'name2' + assert messages[2].string == 'name3' + assert messages[3].string == 'othername3' + for message in messages[:3]: + assert message.language_id == 0 + assert messages[3].language_id == 1 + for id, message in zip((1, 2, 3, 3), messages): + assert message.merge_key == ('Version', id, 'name'), message.key + +def get_messages(*rows): + return list(translations.yield_translation_csv_messages(iter(rows), True)) + +def test_merge_translations(): + source = get_messages( + '0,Table,1,col,,none', + '0,Table,2,col,,new', + '0,Table,3,col,,existing', + '0,Table,4,col,,both', + '0,Table,5,col,,(gap)', + '0,Table,6,col,,new-bad', + '0,Table,7,col,,existing-bad', + '0,Table,8,col,,both-bad', + '0,Table,9,col,,new-bad-ex-good', + '0,Table,10,col,,new-good-ex-bad', + '0,Table,11,col,,(gap)', + '0,Table,12,col,,"Numbers: 1, 2, and 003"', + '0,Table,13,col,,"Numbers: 3, 2, and 001"', + ) + new = get_messages( + '0,Table,2,col,%s,new' % translations.crc('new'), + '0,Table,4,col,%s,new' % translations.crc('both'), + '0,Table,6,col,%s,new' % translations.crc('----'), + '0,Table,8,col,%s,new' % translations.crc('----'), + '0,Table,9,col,%s,new' % translations.crc('----'), + '0,Table,10,col,%s,new' % translations.crc('new-good-ex-bad'), + '0,Table,12,col,%s,{num} {num} {num}' % translations.crc('Numbers: {num}, {num}, and {num}'), + '0,Table,13,col,%s,{num} {num} {num}' % translations.crc('----'), + '0,Table,100,col,%s,unused' % translations.crc('----'), + ) + new[-3].number_replacement = True + new[-3].source = 'Numbers: 1, 2, and 003' + new[-2].number_replacement = True + new[-2].source = '----' + existing = get_messages( + '0,Table,3,col,%s,existing' % translations.crc('existing'), + '0,Table,4,col,%s,existing' % translations.crc('both'), + '0,Table,7,col,%s,existing' % translations.crc('----'), + '0,Table,8,col,%s,existing' % translations.crc('----'), + '0,Table,9,col,%s,existing' % translations.crc('new-bad-ex-good'), + '0,Table,10,col,%s,existing' % translations.crc('----'), + '0,Table,100,col,%s,unused' % translations.crc('----'), + ) + expected_list = ( + ('none', None, None), + ('new', True, 'new'), + ('existing', True, 'existing'), + ('both', True, 'new'), + ('(gap)', None, None), + ('new-bad', False, 'new'), + ('existing-bad', False, 'existing'), + ('both-bad', False, 'new'), + ('new-bad-ex-good', True, 'existing'), + ('new-good-ex-bad', True, 'new'), + ('(gap)', None, None), + ('Numbers: 1, 2, and 003', True, '1 2 003'), + ('Numbers: 3, 2, and 001', False, '3 2 001'), + ) + unused = [] + result_stream = list(translations.merge_translations(source, new, [], existing, unused=unused.append)) + for result, expected in zip(result_stream, expected_list): + res_src, res_crc, res_str, res_match = result + exp_src, exp_match, exp_str = expected + print result, expected + assert res_src.string == exp_src + assert res_str == exp_str, (res_str, exp_str) + if exp_match is None: + assert res_crc is None + elif exp_match is True: + assert res_crc == translations.crc(res_src.string) + elif exp_match is False: + assert res_crc == translations.crc('----') + assert res_match == exp_match + print 'unused:', unused + for message in unused: + assert message.string == 'unused' + assert message.id == 100 + +def test_merge(): + check_merge((0, 1, 2, 3)) + check_merge((0, 1), (2, 3)) + check_merge((2, 3), (0, 1)) + check_merge((0, 2), (1, 3)) + check_merge((0, 3), (1, 2)) + check_merge((0, 1), (2, 3), (2, 3)) + +def check_merge(*sequences): + merged = list(translations.Merge(*sequences)) + concatenated = [val for seq in sequences for val in seq] + assert merged == sorted(concatenated) + +def test_merge_dynamic_add(): + merge = translations.Merge((1, 2, 3)) + def adder(): + for val in (1, 2, 3): + yield val + merge.add_iterator([4]) + merge.add_iterator(adder()) + assert tuple(merge) == (1, 1, 2, 2, 3, 3, 4, 4, 4) + +def test_merge_adjacent(): + messages = get_messages( + '0,Table,1,col,,strA', + '0,Table,2,col,,strB', + '0,Table,2,col,,strC', + '0,Table,2,col,,strB', + '0,Table,2,col,,strD', + '0,Table,3,col,,strE', + ) + result = [m.string for m in translations.merge_adjacent(messages)] + expected = ['strA', 'strB\n\nstrC\n\nstrD', 'strE'] + assert result == expected + +def test_leftjoin(): + check_leftjoin([], [], [], []) + check_leftjoin([], [1], [], [1]) + check_leftjoin([], [1, 2], [], [1, 2]) + check_leftjoin([1], [], [(1, None)], []) + check_leftjoin([1], [1], [(1, 1)], []) + check_leftjoin([1], [2], [(1, None)], [2]) + check_leftjoin([1, 2], [1], [(1, 1), (2, None)], []) + check_leftjoin([1, 2], [1, 2], [(1, 1), (2, 2)], []) + check_leftjoin([1], [1, 2], [(1, 1)], [2]) + check_leftjoin([1, 2], [1, 3], [(1, 1), (2, None)], [3]) + check_leftjoin([1, 2, 3], [1, 3], [(1, 1), (2, None), (3, 3)], []) + check_leftjoin([1, 2, 2, 3], [1, 3], [(1, 1), (2, None), (2, None), (3, 3)], []) + check_leftjoin([1, 2, 2, 3], [2, 2, 2], [(1, None), (2, 2), (2, 2), (3, None)], [2]) + +def check_leftjoin(seqa, seqb, expected, expected_unused): + unused = [] + result = list(translations.leftjoin(seqa, seqb, unused=unused.append)) + assert result == list(expected) + assert unused == list(expected_unused) From 0ed5d65384140e2dc705241c4577b3db29a836d3 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 7 Apr 2011 02:30:31 +0300 Subject: [PATCH 2/6] Add current Czech translations This makes Git track csv/translations --- pokedex/data/csv/translations/cs.csv | 1205 ++++++++++++++++++++++++++ 1 file changed, 1205 insertions(+) create mode 100644 pokedex/data/csv/translations/cs.csv diff --git a/pokedex/data/csv/translations/cs.csv b/pokedex/data/csv/translations/cs.csv new file mode 100644 index 0000000..387b3cc --- /dev/null +++ b/pokedex/data/csv/translations/cs.csv @@ -0,0 +1,1205 @@ +language_id,table,id,column,source_crc,string +10,Ability,1,name,7482ade0,Smrad +10,Ability,1,short_effect,9301d482,Má šanci nepřítele [zastrašit]{mechanic:flinch} během útočení. +10,Ability,2,name,56942be0,Mrholení +10,Ability,2,short_effect,0d8facc9,"Přivolá [déšť]{mechanic:rain}, který trvá až po ukončení zápasu." +10,Ability,3,name,dbbf77d4,Zrychlení +10,Ability,3,short_effect,56aba85b,Zvýší [Rychlost]{mechanic:speed} o jeden [stupeň]{mechanic:stat modifier} po každém kole. +10,Ability,4,name,8598e4a6,Bojové brnění +10,Ability,5,name,1456165e,Robustnost +10,Ability,6,name,fc71d5b7,Vlhkost +10,Ability,7,name,79e4c1ea,Pružnost +10,Ability,8,name,68fb38e0,Pískový plášť +10,Ability,9,name,876c2d13,Statika +10,Ability,10,name,25d248bb,Nabíjení +10,Ability,11,name,c9b017e7,Pohlcení vody +10,Ability,12,name,fe728b73,Nevšímavost +10,Ability,13,name,330485e9,Sedmé nebe +10,Ability,14,name,44379432,Složené oči +10,Ability,15,name,1b22e630,Nespavost +10,Ability,16,name,60f49ede,Změna barvy +10,Ability,17,name,c2bc5d44,Imunita +10,Ability,18,name,f776dc6c,Náhlý požár +10,Ability,19,name,2ba46975,Štítový ptrach +10,Ability,20,name,eec44109,Vlastní tempo +10,Ability,21,name,54175f22,Přísavky +10,Ability,22,name,882269a8,Zastrašení +10,Ability,23,name,e34db84d,Znamení temna +10,Ability,24,name,d17927e6,Drsná kůže +10,Ability,25,name,721a5247,Zázračný štít +10,Ability,26,name,50042f90,Levitace +10,Ability,27,name,9740ab47,Výtrusy +10,Ability,28,name,db646315,Synchronizace +10,Ability,29,name,446fb7b5,Průsvitnost +10,Ability,30,name,f042aa53,Přírodní lék +10,Ability,31,name,12f136d7,Hromosvod +10,Ability,32,name,ff40d1b0,Ušlechtilost +10,Ability,33,name,5ce14a2f,Hbitá plavba +10,Ability,33,short_effect,21665f64,Během [deště]{mechanic:rain} zdvojnásobuje [Rychlost]{mechanic:speed}. +10,Ability,34,name,06f85388,Chlorofyl +10,Ability,34,short_effect,ae2ff9cc,Během [slunečného počasí]{mechanic:strong sunlight} zdvojnásobuje [Rychlost]{mechanic:speed}. +10,Ability,35,name,c6497d6e,Osvětlení +10,Ability,36,name,f09afaa5,Kopírování +10,Ability,37,name,7cb6bfd7,Obrovská síla +10,Ability,38,name,05f68593,Jedovatý roh +10,Ability,39,name,1c7e0068,Soustředění +10,Ability,40,name,a93bb7cf,Lávové brnění +10,Ability,41,name,d5a4bb6e,Vodní rouška +10,Ability,42,name,299a7bfe,Magnetismus +10,Ability,43,name,a3f44988,Hluchota +10,Ability,44,name,5ff669f9,Dešťová miska +10,Ability,45,name,fdc4b2e5,Proud písku +10,Ability,46,name,a6d9c231,Tlak +10,Ability,47,name,4bbe3ccb,Tlustý tuk +10,Ability,48,name,be432c28,Ranní ptáče +10,Ability,49,name,ff7f2a5c,Ohnivé tělo +10,Ability,50,name,18674783,Útěk +10,Ability,51,name,a3268228,Bystré oko +10,Ability,52,name,a07aeadd,Hyperřezač +10,Ability,53,name,46323ccb,Sbírání +10,Ability,54,name,1c2d5a58,Ulejvání +10,Ability,55,name,bd19389f,Čilost +10,Ability,56,name,5cd5f1a2,Roztomilost +10,Ability,57,name,30c04fcf,Plus +10,Ability,58,name,f9629873,Minus +10,Ability,59,name,d3ea1a12,Předpověď +10,Ability,60,name,213ca9bb,Ulepené ruce +10,Ability,61,name,a6fdcb25,Svlékání kůže +10,Ability,62,name,f7e28957,Kuráž +10,Ability,63,name,0d52a696,Zázračná šupina +10,Ability,64,name,bd31f6bf,Hnusný sliz +10,Ability,65,name,1c8c270c,Přerůstání +10,Ability,66,name,34fc1133,Žár +10,Ability,67,name,137a8e2a,Záplava +10,Ability,68,name,28667da5,Rojení +10,Ability,69,name,8c4fcf12,Kamenná hlava +10,Ability,70,name,9b951bc3,Vyprahlost +10,Ability,71,name,32b58a36,Past +10,Ability,72,name,fe8d56b9,Neposednost +10,Ability,73,name,b06fc24d,Bílý kouř +10,Ability,74,name,7c944184,Ryzí síla +10,Ability,75,name,2a253a10,Lasturoštít +10,Ability,76,name,1fe97cce,Vzdušný zámek +10,AbilityChangelog,1,effect,a881640e,Nemá žádný účinek při zápasu. +10,AbilityChangelog,2,effect,757e1dd8,"Funguje jen proti útokům, které vždy omráčí na jeden úder. (Ne proti těm, které jsou prostě příliš silné.)" +10,AbilityChangelog,3,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,4,effect,8742fa05,"Neabsorbuje nezraňující [elektrické]{type:Electric} útoky, např. [Thunder Wave]{move}." +10,AbilityChangelog,7,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,8,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,10,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,13,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,16,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,19,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,20,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,22,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,24,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,25,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,26,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,28,effect,a881640e,Nemá žádný účinek při zápasu. +10,AbilityChangelog,29,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,30,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,31,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,33,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,35,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,36,effect,46cb479e,Nemá žádný účinek v poli. +10,AbilityChangelog,37,effect,46cb479e,Nemá žádný účinek v poli. +10,BerryFirmness,2,name,dfb88a49,Měkká +10,BerryFirmness,3,name,6127b0ec,Tvrdá +10,ContestType,1,flavor,f2a1e3c9,Ostrá +10,ContestType,2,flavor,bc022ee2,Suchá +10,ContestType,2,name,11aabd4e,Krása +10,ContestType,5,name,531bc7f2,Síla +10,EggGroup,1,name,ebe3ff68,pozemní +10,EggGroup,8,name,9b103d6c,antropoidní +10,EggGroup,10,name,d22690e3,minerály +10,EggGroup,11,name,a950b4e7,beztvární +10,EggGroup,13,name,e8d9b1aa,Ditto +10,EggGroup,15,name,b3d9eb9d,nekřižitelní +10,EncounterCondition,1,name,28667da5,Rojení +10,EncounterCondition,2,name,218ba19e,Denní doba +10,EncounterCondition,3,name,006244fe,PokéRadar +10,EncounterCondition,5,name,2187340b,Rádio +10,EncounterConditionValue,1,name,c62f244f,Během rojení +10,EncounterConditionValue,2,name,28be2d2f,Ne během rojení +10,EncounterConditionValue,3,name,95bf5e60,Ráno +10,EncounterConditionValue,4,name,d4461d1a,Ve dne +10,EncounterConditionValue,5,name,4866149e,V noci +10,EncounterConditionValue,6,name,7d81d602,Pomocí PokéRadaru +10,EncounterConditionValue,7,name,61a0eb90,Bez PokéRadaru +10,EncounterConditionValue,14,name,994320ec,Vypnuté rádio +10,EncounterConditionValue,15,name,0e34be45,Hoennské rádio +10,EncounterConditionValue,16,name,d07cd16a,Sinnohské rádio +10,Generation,1,name,bbda05fc,I. generace +10,Generation,2,name,69b2728b,II. generace +10,Generation,3,name,a701fe63,III. generace +10,Generation,4,name,e4ba7f7e,IV. generace +10,Generation,5,name,36d20809,V. generace +10,GrowthRate,1,name,350771dd,pomalá +10,GrowthRate,2,name,c67345b7,průměrná +10,GrowthRate,3,name,256649c4,rychlá +10,GrowthRate,4,name,d69aac12,celkem pomalá +10,Item,1,effect,04883b3a,"Během zápasu: Chytí jednoho Pokémona, bez možnosti neúspěchu. Má stupeň chytitelnosti 255." +10,Item,305,name,93f16fb3,TM01 +10,Item,306,name,0af83e09,TM02 +10,Item,307,name,7dff0e9f,TM03 +10,Item,308,name,e39b9b3c,TM04 +10,Item,309,name,949cabaa,TM05 +10,Item,310,name,0d95fa10,TM06 +10,Item,311,name,7a92ca86,TM07 +10,Item,312,name,ea2dd717,TM08 +10,Item,313,name,9d2ae781,TM09 +10,Item,314,name,fded6e64,TM10 +10,Item,315,name,8aea5ef2,TM11 +10,Item,316,name,13e30f48,TM12 +10,Item,317,name,64e43fde,TM13 +10,Item,318,name,fa80aa7d,TM14 +10,Item,319,name,8d879aeb,TM15 +10,Item,320,name,148ecb51,TM16 +10,Item,321,name,6389fbc7,TM17 +10,Item,322,name,f336e656,TM18 +10,Item,323,name,8431d6c0,TM19 +10,Item,324,name,d6c03da7,TM20 +10,Item,325,name,a1c70d31,TM21 +10,Item,326,name,38ce5c8b,TM22 +10,Item,327,name,4fc96c1d,TM23 +10,Item,328,name,d1adf9be,TM24 +10,Item,329,name,a6aac928,TM25 +10,Item,330,name,3fa39892,TM26 +10,Item,331,name,48a4a804,TM27 +10,Item,332,name,d81bb595,TM28 +10,Item,333,name,af1c8503,TM29 +10,Item,334,name,cfdb0ce6,TM30 +10,Item,335,name,b8dc3c70,TM31 +10,Item,336,name,21d56dca,TM32 +10,Item,337,name,56d25d5c,TM33 +10,Item,338,name,c8b6c8ff,TM34 +10,Item,339,name,bfb1f869,TM35 +10,Item,340,name,26b8a9d3,TM36 +10,Item,341,name,51bf9945,TM37 +10,Item,342,name,c10084d4,TM38 +10,Item,343,name,b607b442,TM39 +10,Item,344,name,809a9a21,TM40 +10,Item,345,name,f79daab7,TM41 +10,Item,346,name,6e94fb0d,TM42 +10,Item,347,name,1993cb9b,TM43 +10,Item,348,name,87f75e38,TM44 +10,Item,349,name,f0f06eae,TM45 +10,Item,350,name,69f93f14,TM46 +10,Item,351,name,1efe0f82,TM47 +10,Item,352,name,8e411213,TM48 +10,Item,353,name,f9462285,TM49 +10,Item,354,name,9981ab60,TM50 +10,Item,355,name,ee869bf6,TM51 +10,Item,356,name,778fca4c,TM52 +10,Item,357,name,0088fada,TM53 +10,Item,358,name,9eec6f79,TM54 +10,Item,359,name,e9eb5fef,TM55 +10,Item,360,name,70e20e55,TM56 +10,Item,361,name,07e53ec3,TM57 +10,Item,362,name,975a2352,TM58 +10,Item,363,name,e05d13c4,TM59 +10,Item,364,name,b2acf8a3,TM60 +10,Item,365,name,c5abc835,TM61 +10,Item,366,name,5ca2998f,TM62 +10,Item,367,name,2ba5a919,TM63 +10,Item,368,name,b5c13cba,TM64 +10,Item,369,name,c2c60c2c,TM65 +10,Item,370,name,5bcf5d96,TM66 +10,Item,371,name,2cc86d00,TM67 +10,Item,372,name,bc777091,TM68 +10,Item,373,name,cb704007,TM69 +10,Item,374,name,abb7c9e2,TM70 +10,Item,375,name,dcb0f974,TM71 +10,Item,376,name,45b9a8ce,TM72 +10,Item,377,name,32be9858,TM73 +10,Item,378,name,acda0dfb,TM74 +10,Item,379,name,dbdd3d6d,TM75 +10,Item,380,name,42d46cd7,TM76 +10,Item,381,name,35d35c41,TM77 +10,Item,382,name,a56c41d0,TM78 +10,Item,383,name,d26b7146,TM79 +10,Item,384,name,2c2fd52d,TM80 +10,Item,385,name,5b28e5bb,TM81 +10,Item,386,name,c221b401,TM82 +10,Item,387,name,b5268497,TM83 +10,Item,388,name,2b421134,TM84 +10,Item,389,name,5c4521a2,TM85 +10,Item,390,name,c54c7018,TM86 +10,Item,391,name,b24b408e,TM87 +10,Item,392,name,22f45d1f,TM88 +10,Item,393,name,55f36d89,TM89 +10,Item,394,name,3534e46c,TM90 +10,Item,395,name,4233d4fa,TM91 +10,Item,396,name,db3a8540,TM92 +10,Item,397,name,893e8794,HM01 +10,Item,398,name,1037d62e,HM02 +10,Item,399,name,6730e6b8,HM03 +10,Item,400,name,f954731b,HM04 +10,Item,401,name,8e53438d,HM05 +10,Item,402,name,175a1237,HM06 +10,Item,403,name,605d22a1,HM07 +10,Item,404,name,f0e23f30,HM08 +10,Item,659,name,ac3db5d6,TM93 +10,Item,660,name,32592075,TM94 +10,Item,661,name,455e10e3,TM95 +10,ItemCategory,4,name,18991a24,Zbytek +10,ItemCategory,5,name,36935179,V nouzi +10,ItemCategory,7,name,4ff9613b,Typová protekce +10,ItemCategory,8,name,04301177,Jen k pečení +10,ItemCategory,9,name,a928473b,Zběratelské +10,ItemCategory,11,name,6b737987,Do jeskyní +10,ItemCategory,16,name,2c64e8d9,Trénink +10,ItemCategory,17,name,e06adc96,Desky +10,ItemCategory,18,name,6865b70c,Druhové +10,ItemCategory,20,name,dced68a2,Eventové +10,ItemCategory,21,name,f1ceb6d3,Herní +10,ItemCategory,22,name,62cf09e3,Příběhové +10,ItemCategory,23,name,93804dcf,Nepoužité +10,ItemCategory,24,name,060076c9,Kořist +10,ItemCategory,26,name,609c84ea,Vitamíny +10,ItemCategory,27,name,08bd2eb2,Léčící +10,ItemCategory,28,name,bebd2483,Léčící PP +10,ItemCategory,29,name,293d7798,Oživující +10,ItemCategory,32,name,76d4d801,Hnojivo +10,ItemCategory,42,name,f663e557,Drahokamy +10,ItemFlag,1,name,b4787086,Počítatelný +10,ItemFlag,2,description,5159dc6d,Spotřebuje se po použití +10,ItemFlag,2,name,0b28f345,Spotřebující se +10,ItemFlag,3,description,84964751,Použitelný mimo souboj +10,ItemFlag,4,description,2d26fde6,Použitelný v souboji +10,ItemFlag,5,description,24c931a8,Může být držen pokémonem +10,ItemFlag,5,name,cd791ac5,Držitelný +10,ItemFlag,6,description,725033e7,Funguje pasivně při držení +10,ItemFlag,7,description,93e707b2,Použitelný pokémonem při držení +10,ItemFlag,8,description,073d76cb,Nachází se v Sinnohském podzemí +10,ItemFlag,8,name,590e69f7,Podzemí +10,ItemFlingEffect,1,effect,343663e5,Těžce otráví protivníka. +10,ItemPocket,1,name,20dfc649,Předměty +10,ItemPocket,2,name,a14048db,Medicína +10,ItemPocket,3,name,2c755235,Poké Bally +10,ItemPocket,4,name,d9119462,TMka a HMka +10,ItemPocket,5,name,c265b1b4,Bobule +10,ItemPocket,6,name,f1140376,Dopisy +10,Language,1,name,f3e12338,Japonština +10,Language,3,name,96f9ccf5,Korejština +10,Language,4,name,34251e88,Čínština +10,Language,5,name,a8d9cb68,Francouzština +10,Language,6,name,a5984597,Němčina +10,Language,7,name,fba5cca8,Španělština +10,Language,8,name,98045053,Italština +10,Language,9,name,e33e3b9c,Angličtina +10,Location,61,name,b3e82591,Námořní cesta 220 +10,Location,62,name,2ae1742b,Námořní cesta 223 +10,Location,63,name,5a8b80a4,Námořní cesta 226 +10,Location,64,name,aaf314d0,Námořní cesta 230 +10,Location,98,name,3dca45a5,Námořní cesta 19 +10,Location,100,name,6f3baec2,Námořní cesta 20 +10,Location,101,name,183c9e54,Námořní cesta 21 +10,Location,121,name,39610944,Námořní cesta 40 +10,Location,122,name,4e6639d2,Námořní cesta 41 +10,LocationArea,14,name,f430ffe2,chumelenice +10,LocationArea,15,name,3d22a718,vánice +10,LocationArea,24,name,7763497b,Oblast 1 +10,LocationArea,25,name,ee6a18c1,Oblast 2 +10,LocationArea,26,name,996d2857,Oblast 3 +10,LocationArea,27,name,0709bdf4,Oblast 4 +10,LocationArea,28,name,700e8d62,Oblast 5 +10,LocationArea,29,name,e907dcd8,Oblast 6 +10,LocationArea,52,name,bafef2b4,uvnitř +10,LocationArea,58,name,87da6189,vchod +10,LocationArea,59,name,bafef2b4,uvnitř +10,LocationArea,61,name,e41d1a62,1. sloup +10,LocationArea,62,name,7d144bd8,2. sloup +10,LocationArea,63,name,0a137b4e,3. sloup +10,LocationArea,126,name,87da6189,vchod +10,LocationArea,135,name,9cc24903,před zásahem týmu Galactic +10,LocationArea,136,name,476b4de4,po zásahu týmu Galactic +10,LocationArea,144,name,aa83e7d8,"jižně, směrem k Jubilife" +10,LocationArea,145,name,eadc8cd2,"severně, směrem k Floaromě" +10,LocationArea,146,name,238dc7a5,"jižně, směrem k Floaromě" +10,LocationArea,147,name,3a9d6056,"východně, směrem k Eterně" +10,LocationArea,157,name,286b8f9b,"jižně, směrem k Solaceonu" +10,LocationArea,158,name,58f024e4,"západně, směrem k Celesticu" +10,LocationArea,159,name,9beaff3a,"západně, směrem k Eterně" +10,LocationArea,160,name,f1082fed,"východně, směrem k Celesticu" +10,LocationArea,161,name,f28baead,"severně, směrem k Hearthome" +10,LocationArea,162,name,ae4fd824,"východně, směrem k Pastorii" +10,LocationArea,193,name,7a57c43a,Venku +10,LocationArea,194,name,96b7553b,Vnitřek A +10,LocationArea,195,name,0fbe0481,Vnitřek B +10,LocationArea,196,name,78b93417,Vnitřek C +10,LocationArea,197,name,e6dda1b4,Vnitřek D +10,LocationArea,229,name,4695d28d,prázdné patro X +10,LocationArea,231,name,3192e21b,prázdné patro Y +10,LocationArea,238,name,98efa44b,Spodní jeskyně +10,LocationArea,239,name,3ae88ad0,Vrchní jeskyně +10,LocationArea,253,name,8f00d6f7,vstup do Violetu +10,LocationArea,254,name,74f25a17,vstup do Blackthornu +10,LocationArea,266,name,10891a9e,vchod do jeskyně +10,LocationArea,267,name,4b800254,v jeskyni +10,LocationArea,269,name,b5eafda6,venku +10,LocationArea,271,name,842256b1,podél jeskyně +10,LocationArea,273,name,1ed91fca,vrcholek +10,LocationArea,296,name,3762f01a,"jižně, směrem k Viridianu" +10,LocationArea,320,name,28233684,"severně, směrem k Pewteru" +10,LocationArea,345,name,9e801acf,uprostřed +10,LocationArea,349,name,3e9db05c,Přístav S.S. Anne +10,LocationArea,374,name,b5eafda6,venku +10,LocationArea,377,name,87da6189,vchod +10,LocationArea,490,name,bafef2b4,uvnitř +10,LocationArea,496,name,87da6189,vchod +10,Move,1,name,7eef9650,Facka +10,Move,2,name,9e0525b5,Karatistický úder +10,Move,3,name,95eacd2f,Dvojitá facka +10,Move,4,name,056f009c,Opakovaný úder +10,Move,5,name,457b6309,Megarána +10,Move,6,name,0b4d71d4,Výplata +10,Move,7,name,9bbb70b6,Ohnivý úder +10,Move,8,name,3f7cc89f,Ledový úder +10,Move,9,name,cab90d03,Blesková rána +10,Move,10,name,5ff863eb,Škrábnutí +10,Move,11,name,827bd4bf,Svěrák +10,Move,12,name,a7c35413,Klepetí gilotina +10,Move,13,name,724bbda2,Ostrý vítr +10,Move,14,name,88b5f9fa,Tanec mečů +10,Move,15,name,88bdd21d,Sek +10,Move,16,name,26c78a33,Vzdušný vír +10,Move,17,name,676c1557,Křídlový útok +10,Move,18,name,f77d374d,Odfouknutí +10,Move,19,name,6bc7c553,Let +10,Move,20,name,e6fe5c6d,Omotání +10,Move,21,name,f6d40a17,Náraz +10,Move,22,name,9eb01dc6,Úponkový bič +10,Move,23,name,9e119d06,Dupnutí +10,Move,24,name,4f4d9d7b,Dvojitý kop +10,Move,25,name,82047096,Megakop +10,Move,26,name,f624a204,Kop z výskoku +10,Move,27,name,468bc5a5,Kop s otočkou +10,Move,28,name,2b3a4002,Písečný útok +10,Move,29,name,36e0f4e5,Náraz hlavou +10,Move,30,name,02c7ff53,Rohový útok +10,Move,31,name,1643fd46,Zuřivý útok +10,Move,32,name,7962aadd,Vrtání rohem +10,Move,33,name,1b0e78ef,Nárazový útok +10,Move,34,name,5a2343c8,Zavalení +10,Move,35,name,0c087fe3,Ovinutí +10,Move,36,name,5836ed1a,Sejmutí +10,Move,37,name,40c7dd91,Zuřivost +10,Move,38,name,dc0e55a9,Dvojí ostří +10,Move,39,name,810b41b3,Vrtění ocasem +10,Move,40,name,41f06e03,Jedovaté žihadlo +10,Move,41,name,a596d6cd,Dvojehla +10,Move,42,name,2d55e587,Jehlostřela +10,Move,43,name,181bbea0,Lstivý pohled +10,Move,44,name,21d49620,Kousnutí +10,Move,45,name,8a36550c,Zavrčení +10,Move,46,name,c126dede,Řev +10,Move,47,name,97523d2d,Zpěv +10,Move,48,name,c753f6d3,Ultrazvuk +10,Move,49,name,2d0dc99b,Zvuková vlna +10,Move,50,name,80f874cd,Zákaz +10,Move,51,name,b69de092,Kyselina +10,Move,52,name,72af9f46,Popálení +10,Move,53,name,1eafab30,Plamenomet +10,Move,54,name,5c463043,Mlha +10,Move,55,name,02d2b4dd,Vodní dělo +10,Move,56,name,3cb366ab,Vodní pumpa +10,Move,57,name,131e0af2,Surfování +10,Move,58,name,40b23f44,Ledový paprsek +10,Move,59,name,c454c54e,Vánice +10,Move,60,name,571bba58,Psychický paprsek +10,Move,61,name,53162a19,Bublinomet +10,Move,62,name,f2c58cdc,Polární záře +10,Move,63,name,f0f6f5ce,Hyperpaprsek +10,Move,64,name,30e559c1,Klovnutí +10,Move,65,name,866adf06,Nebozezobák +10,Move,66,name,94585923,Znehybnění +10,Move,67,name,2eab899d,Nízký kop +10,Move,68,name,0e9fade4,Odvetný úder +10,Move,69,name,25f454b4,Seismický vrh +10,Move,70,name,2fcf838e,Síla +10,Move,71,name,1280c428,Vstřebání +10,Move,72,name,551c6070,Megavysátí +10,Move,73,name,b33a8203,Pijavičné semínko +10,Move,74,name,1a4ce4c0,Růst +10,Move,75,name,95dfe9bb,Listová břitva +10,Move,76,name,98554cb5,Sluneční paprsek +10,Move,77,name,ba4737d8,Jedovatý pyl +10,Move,78,name,53a57da7,Omračující pyl +10,Move,79,name,207275ff,Uspávací pyl +10,Move,80,name,c1eb97d1,Tanec listů +10,Move,81,name,4140a02f,Lepivé vlákno +10,Move,82,name,df28c1ca,Dračí hněv +10,Move,83,name,f556f1f1,Ohnivý vír +10,Move,84,name,b7a1c31c,Bleskový šok +10,Move,85,name,f6d30876,Bleskový útok +10,Move,86,name,7b0857ed,Blesková vlna +10,Move,87,name,adc59c6c,Blesk +10,Move,88,name,45b85329,Hod kamenem +10,Move,89,name,a863c1ea,Zemětřesení +10,Move,90,name,205f70da,Puklina v zemi +10,Move,91,name,ef3bd81b,Hrabavý útok +10,Move,92,name,5292a0a7,Toxický útok +10,Move,93,name,4a28e9d1,Zmatení +10,Move,94,name,c81ee961,Psychika +10,Move,95,name,834b9610,Hypnóza +10,Move,96,name,d2b08c9e,Meditace +10,Move,97,name,aabf4613,Hbitost +10,Move,98,name,a8f0b0b9,Hbitý útok +10,Move,99,name,1e31d195,Hněv +10,Move,100,name,21f1f4db,Teleportace +10,Move,101,name,12b1fa46,Noční stín +10,Move,102,name,00e46b13,Napodobení +10,Move,103,name,cd7e3dfb,Zaskřípání +10,Move,104,name,9578e461,Zdvojení +10,Move,105,name,3a35c8d1,Obnovení +10,Move,106,name,caf3af09,Zatvrzení +10,Move,107,name,090aa1af,Zmenšení +10,Move,108,name,1035d176,Kouřová clona +10,Move,109,name,7ee5dcb5,Matoucí paprsek +10,Move,110,name,4ca83dc8,Schování +10,Move,111,name,a0b162fb,Schoulení +10,Move,112,name,2ef3c334,Bariéra +10,Move,113,name,3f20cb45,Světelná zeď +10,Move,114,name,def90a72,Opar +10,Move,115,name,4139f7d5,Zrcadlová zeď +10,Move,116,name,2b5efd83,Soustředění +10,Move,117,name,6b168471,Výzva +10,Move,119,name,ff144f98,Zrcadlový útok +10,Move,120,name,736ed3b7,Sebezničení +10,Move,121,name,9f077858,Vaječná puma +10,Move,122,name,23304882,Oblíznutí +10,Move,123,name,8940a4b0,Smog +10,Move,124,name,63411a53,Bahno +10,Move,125,name,644f433d,Kostěný kyj +10,Move,126,name,f915a562,Výbuch ohně +10,Move,127,name,f563415a,Vodopád +10,Move,128,name,61c258fe,Sevření +10,Move,129,name,6cb05388,Hvězdičky +10,Move,130,name,46bde1fa,Úder lebkou +10,Move,131,name,9621c0f2,Ostnové dělo +10,Move,132,name,cd38825a,Škrcení +10,Move,133,name,39bd2403,Amnézie +10,Move,134,name,f5e54813,Kineze +10,Move,135,name,3738b8ef,Na měkko +10,Move,136,name,39531aa6,Vysoký kop +10,Move,137,name,34c5144b,Hadí pohled +10,Move,138,name,fe22cad8,Požírač snů +10,Move,139,name,74cdebd5,Jedovatý plyn +10,Move,140,name,910575c9,Bombardování +10,Move,141,name,ee705ffb,Vysátí +10,Move,142,name,eb7c0105,Polibek +10,Move,143,name,e29370cd,Nebeský útok +10,Move,144,name,f543030e,Transformace +10,Move,145,name,ec8cf4c1,Bublinky +10,Move,146,name,2da708a7,Zmatený úder +10,Move,147,name,b1f4e024,Výtrus +10,Move,148,name,6e0f7007,Záblesk +10,Move,149,name,3c8db98d,Psy-vlna +10,Move,150,name,bd9411c6,Šplouchnutí +10,Move,151,name,f829493c,Rozpuštění +10,Move,152,name,270de9fa,Krabí kladivo +10,Move,153,name,f366e3bf,Exploze +10,Move,154,name,e55c5dd3,Zuřivé řezání +10,Move,155,name,a3c3bf3e,Kostmerang +10,Move,156,name,5d268eee,Odpočinek +10,Move,157,name,78c4fc7c,Pád skály +10,Move,158,name,1fd03f94,Hypertesák +10,Move,159,name,e8e32c64,Nabroušení +10,Move,160,name,f2cc2494,Konverze +10,Move,161,name,7c97aeb5,Trojitý útok +10,Move,162,name,26aab69f,Supertesák +10,Move,163,name,c60f68f5,Šmik +10,Move,164,name,80ae5069,Zástupce +10,Move,165,name,ce8dbf62,Zoufalý útok +10,Move,166,name,87838b15,Náčrt +10,Move,167,name,4781d4db,Trojitý kop +10,Move,168,name,0e7eeada,Zloděj +10,Move,169,name,588b6055,Pavučina +10,Move,170,name,2e452afc,Čtení mysli +10,Move,171,name,3991ce92,Noční můra +10,Move,172,name,2646b72a,Ohnivý kruh +10,Move,173,name,0132c088,Chrápání +10,Move,174,name,eb2f86f6,Prokletí +10,Move,175,name,d94f4ec5,Škubání +10,Move,177,name,bdd38329,Vzdušný výbuch +10,Move,178,name,0fd1e10a,Chmýří +10,Move,179,name,886597fa,Zvrat +10,Move,180,name,e3233b10,Zlá vůle +10,Move,181,name,1ae60675,Prašan +10,Move,182,name,50c8c9de,Protekce +10,Move,183,name,0945792c,Hbitá pěst +10,Move,184,name,da7ff133,Děsivý škleb +10,Move,185,name,9a8bc753,Matný útok +10,Move,186,name,f391734f,Sladký polibek +10,Move,187,name,6bc7786a,Břišní buben +10,Move,188,name,79050c5f,Bahenní puma +10,Move,189,name,7748e8f2,Plesknutí blátem +10,Move,190,name,4dd92763,Chobozuka +10,Move,191,name,11201819,Střepiny +10,Move,192,name,9908380e,Výbojové dělo +10,Move,193,name,2e0c2866,Vidění +10,Move,194,name,e3f4dc3e,Pouto osudu +10,Move,195,name,7f7b0539,Píseň zániku +10,Move,196,name,a43f0706,Severák +10,Move,197,name,c720a2a5,Detekce +10,Move,198,name,2992a5a0,Příval kostí +10,Move,199,name,38aeca45,Zaměření +10,Move,200,name,e7d5a124,Rozzuření +10,Move,201,name,ced59494,Písečná bouře +10,Move,202,name,a40a03ad,Giga-vysátí +10,Move,203,name,0efab71c,Výdrž +10,Move,204,name,40fcadee,Šarm +10,Move,205,name,bb1dfc13,Valivý útok +10,Move,206,name,f14fbe09,Falešný sek +10,Move,207,name,70ccd23d,Naparování +10,Move,208,name,7b0ae38c,Doušek mléka +10,Move,209,name,5cd2e029,Jiskra +10,Move,210,name,4e371b20,Zuřivé nože +10,Move,211,name,dae09f3a,Ocelové křídlo +10,Move,212,name,256dfc7e,Zlý pohled +10,Move,213,name,726aec2a,Zalíbení +10,Move,214,name,9eca6d4c,Řeč ze sna +10,Move,215,name,7a332cbf,Léčivý zvonek +10,Move,216,name,a0323a39,Opětování +10,Move,217,name,3201978b,Dárek +10,Move,218,name,2e86dd50,Frustrace +10,Move,219,name,68f8cadf,Ochrana +10,Move,220,name,b411b0cf,Půlená bolest +10,Move,221,name,6de736ec,Posvátný oheň +10,Move,222,name,b776ffa2,Magnituda +10,Move,223,name,b94bee5b,Výbušný úder +10,Move,224,name,b58d0ed2,Megaroh +10,Move,225,name,b6ce4c28,Dračí dech +10,Move,226,name,db006c42,Štafeta +10,Move,227,name,829ca903,Přídavek +10,Move,228,name,aea3d916,Pronásledování +10,Move,229,name,66d505a7,Kolotoč +10,Move,230,name,bb101823,Sladká vůně +10,Move,231,name,09cd8e42,Železný ocas +10,Move,232,name,5ffd637d,Kovový dráp +10,Move,233,name,64ba0144,Osudný vrh +10,Move,234,name,7ccbd159,Ranní slunce +10,Move,235,name,dbcd8615,Syntéza +10,Move,236,name,2227bb25,Měsíční svit +10,Move,237,name,57e79e96,Skrytá síla +10,Move,238,name,ad6b7c53,Křížový úder +10,Move,239,name,44007a93,Tornádo +10,Move,240,name,195a078c,Tanec v dešti +10,Move,241,name,d64d3499,Slunečný den +10,Move,242,name,a80d0d44,Křupnutí +10,Move,243,name,70f89ccf,Zrcadlo +10,Move,244,name,4e2df6ac,Nápsych +10,Move,245,name,94954074,Extrémní rychlost +10,Move,246,name,67f9074b,Prastará síla +10,Move,247,name,080e63e3,Koule stínů +10,Move,248,name,d076fd92,Budoucnost +10,Move,249,name,b559328a,Drcení kamenů +10,Move,250,name,79e7ca35,Vodní vír +10,Move,251,name,f94ad23c,Zbití +10,Move,252,name,4a2409a3,Předstírání +10,Move,253,name,540376d4,Hluk +10,Move,254,name,4019109c,Zásobení +10,Move,255,name,5b889daf,Vyplivnutí +10,Move,256,name,211da6d8,Spolknutí +10,Move,257,name,3c89e147,Tepelná vlna +10,Move,258,name,c6caf344,Kroupy +10,Move,259,name,1b00c9e3,Trápení +10,Move,260,name,70f77654,Lichotka +10,Move,261,name,061d0658,Bludička +10,Move,262,name,59c4c7f2,Odkaz +10,Move,263,name,905ed4a1,Vzteklý útok +10,Move,264,name,05e678e9,Mířená rána +10,Move,265,name,a8e2c148,Smradlavá sůl +10,Move,266,name,dca2af42,Za mnou +10,Move,267,name,ea756121,Přírodní síla +10,Move,268,name,52c7a102,Nabití +10,Move,269,name,760757c7,Navádění +10,Move,270,name,3148b453,Pomocná ruka +10,Move,271,name,1931861a,Trik +10,Move,272,name,b977f6ad,Vžití do role +10,Move,273,name,77e3dbf7,Přání +10,Move,274,name,a64282b8,Asistence +10,Move,275,name,c3057f12,Zakořenění +10,Move,276,name,55017b02,Supersíla +10,Move,277,name,a9d317e8,Magický plášť +10,Move,278,name,07bb9f87,Recyklace +10,Move,279,name,5e5825ca,Odplata +10,Move,280,name,3c39412a,Rozbití cihly +10,Move,281,name,14209d4d,Zívnutí +10,Move,282,name,2e5151d6,Vyražení +10,Move,283,name,92e972d5,Úsilí +10,Move,284,name,36a1520c,Erupce +10,Move,285,name,f63fc223,Výměna +10,Move,286,name,6957c053,Uvěznění +10,Move,287,name,af3c9967,Osvěžení +10,Move,288,name,24d6733d,Zášť +10,Move,289,name,c0019332,Šlohnutí +10,Move,290,name,dedfa6a1,Tajemná síla +10,Move,291,name,3689ab7e,Potopení +10,Move,292,name,36df489c,Úder paží +10,Move,293,name,891357d0,Kamufláž +10,Move,294,name,1e258cd5,Ocasní světélko +10,Move,295,name,b14b4fea,Očistná záře +10,Move,296,name,7fc6d972,Mlhová koule +10,Move,297,name,95a04262,Tanec peří +10,Move,298,name,04528f16,Bláznivé tóny +10,Move,299,name,9190fcd1,Žárový kop +10,Move,300,name,48573501,Blátivá hra +10,Move,301,name,8512d943,Ledová koule +10,Move,302,name,437b24e3,Trnitá ruka +10,Move,303,name,2b53cd9f,Zalenošení +10,Move,304,name,f9bbce08,hlaszvuk +10,Move,305,name,b0c9fbbf,Jedovatý tesák +10,Move,306,name,2eefb319,Drtivá tlapa +10,Move,307,name,8705aa3d,Ohnivý výšleh +10,Move,308,name,f4bbd4d7,Hydrodělo +10,Move,309,name,e9144b5c,Meteorána +10,Move,310,name,0dc0cb27,Vyděšení +10,Move,311,name,2930d1fa,Koule počasí +10,Move,312,name,c44bb7b5,Aromaterapie +10,Move,313,name,34eea430,Falešné slzy +10,Move,314,name,ea20f3ae,Sek vzduchem +10,Move,315,name,5a0cfe9b,Přehřátí +10,Move,316,name,d40901cc,Pachová stopa +10,Move,317,name,dec148da,Kamenná past +10,Move,318,name,89990b84,Stříbrný vítr +10,Move,319,name,444c20f6,Kovový zvuk +10,Move,320,name,04a144bf,Travní hvizd +10,Move,321,name,2b7e332e,Polechtání +10,Move,322,name,9169a89e,Kosmická síla +10,Move,323,name,1f1f4f3b,Vodotrysk +10,Move,324,name,f998029d,Signální záře +10,Move,325,name,e92777cd,Temná pěst +10,Move,326,name,0728175d,Mimosmyslové vnímání +10,Move,327,name,eeb18e93,Nebeský úder +10,Move,328,name,4cb19dbe,Písečné peklo +10,Move,329,name,d901c2f6,Totální mráz +10,Move,330,name,b199893c,Kalná voda +10,Move,331,name,85dd08ef,Semenomet +10,Move,332,name,4955fc11,Vzdušné eso +10,Move,333,name,962eacd6,Rampouchové kopí +10,Move,334,name,b41ed436,Zaželezení +10,Move,335,name,42dab826,Zablokování +10,Move,336,name,1815e191,Zavytí +10,Move,337,name,9ab6b4ad,Dračí Spár +10,Move,338,name,4e8662b2,Šílená kytka +10,Move,339,name,0a7653df,Nadmutí +10,Move,340,name,8590f16f,Odražení +10,Move,341,name,113f6d08,Bahnomet +10,Move,342,name,924a2be4,Jedovatý ocas +10,Move,343,name,a5aa0cf4,Chamtivost +10,Move,344,name,2c5cf47c,Voltonáraz +10,Move,345,name,0d036196,Magický list +10,Move,346,name,505ed9fc,Vodní hra +10,Move,347,name,bb330d7f,Klidná mysl +10,Move,348,name,a4ec4e8e,Ostrý list +10,Move,349,name,229f871e,Dračí tanec +10,Move,350,name,4464b792,SuprŠutr +10,Move,351,name,1d079fd8,Šoková vlna +10,Move,352,name,51cdfcf4,Vodní puls +10,Move,353,name,f13587c6,Vůle osudu +10,Move,354,name,6707c6e7,Psyzvýšení +10,Move,355,name,f7459fee,Hřadování +10,Move,356,name,95a01c3b,Gravitace +10,Move,357,name,184157d1,Zázračné oko +10,Move,358,name,f0742df8,Facka na probuzení +10,Move,359,name,a03b4194,Kladivová pěst +10,Move,360,name,59359b2b,Gyrokáča +10,Move,361,name,7ef1b8c0,Léčivé přání +10,Move,362,name,a487b772,Slaná voda +10,Move,363,name,2e0290b7,Dar přírody +10,Move,364,name,f6795e66,Lest +10,Move,365,name,494b5a62,Vyklovnutí +10,Move,366,name,eaf92a38,Vítr v zádech +10,Move,367,name,c6283124,Akupresura +10,Move,368,name,c1fc43c2,Ocelový výbuch +10,Move,369,name,d5b41909,Otočka +10,Move,370,name,e3add733,Boj na blízko +10,Move,371,name,c55976a3,Vyrovnání účtů +10,Move,372,name,ba99ab0d,Ujištění +10,Move,373,name,ff9910f8,Embargo +10,Move,374,name,0fcf5032,Hod +10,Move,375,name,9468daca,Psychopřesun +10,Move,376,name,de70ddf2,Trumf +10,Move,377,name,ab102770,Blokace léčení +10,Move,378,name,c7593fe8,Vyždímání +10,Move,379,name,9c5486c5,Trik síly +10,Move,380,name,9fa8deb3,Žaludeční šťáva +10,Move,381,name,35444a34,Popěvek pro štěstí +10,Move,382,name,9c0a586f,Nejdřív já +10,Move,383,name,9fd79876,Opičení +10,Move,384,name,85912d0d,Výměna síly +10,Move,385,name,b42f0419,Výměna obrany +10,Move,386,name,345745e1,Co proto +10,Move,387,name,c78d7093,Záloha +10,Move,388,name,86070cf4,Semínko starostí +10,Move,389,name,94f1d282,Podpásovka +10,Move,390,name,a3e1100f,Toxické ostny +10,Move,391,name,ea321f7e,Výměna srdcí +10,Move,392,name,fd891bc7,Vodní kruh +10,Move,393,name,22295f5b,Magnetický zdvih +10,Move,394,name,0aa8742d,Plamenný nálet +10,Move,395,name,4674b767,Použití Síly +10,Move,396,name,821f89e7,Kulová aura +10,Move,397,name,2a0a21c4,Broušení kamene +10,Move,398,name,16a55874,Jedové bodnutí +10,Move,399,name,435ec34c,Temný puls +10,Move,400,name,e6e1953a,Rozpárání +10,Move,401,name,0e625aec,Vodní ocas +10,Move,402,name,bf8ecffd,Semínková puma +10,Move,403,name,9c32e3a6,Vzdušný sek +10,Move,404,name,36dca5a6,X-Šmik +10,Move,405,name,62ddc6c0,Zabzučení +10,Move,406,name,21c2b0ab,Dračí puls +10,Move,407,name,9418618e,Dračí zteč +10,Move,408,name,911a9250,Síla drahokamu +10,Move,409,name,8c313a9c,Vysávací úder +10,Move,410,name,d47efaf7,Vlna vzduchoprázdna +10,Move,411,name,6748ad3d,Usměrnění Síly +10,Move,412,name,ad8e5b9b,Míč energie +10,Move,413,name,8c5c7e4d,Nálet +10,Move,414,name,176ec13b,Síla Země +10,Move,415,name,b12f455f,Zamíchání +10,Move,416,name,209f837c,Meganáraz +10,Move,417,name,c038246f,Intrika +10,Move,418,name,f5f6706f,Výstřelový úder +10,Move,419,name,b2eecc41,Lavina +10,Move,420,name,8b9c150b,Střechýle +10,Move,421,name,87a12074,Stínový dráp +10,Move,422,name,ffb89e62,Hromový tesák +10,Move,423,name,af94f51e,Ledový tesák +10,Move,424,name,b9e8b178,Ohnivý tesák +10,Move,425,name,8ef5015a,Plížení stínem +10,Move,426,name,3a379ca4,Blátivá puma +10,Move,427,name,b2fa0477,Psychosek +10,Move,428,name,9207d394,Zenová hlavička +10,Move,429,name,bb2df05b,Zrcadlová střela +10,Move,430,name,39bcb95e,Světelné dělo +10,Move,431,name,57824514,Horolezectví +10,Move,432,name,1a403aa4,Odmlžení +10,Move,433,name,a8c1b9db,Komnata triků +10,Move,434,name,67784081,Dračí meteor +10,Move,435,name,93fa3214,Vybití +10,Move,436,name,9a146d39,Výbuch lávy +10,Move,437,name,741c9f23,Listová bouře +10,Move,438,name,d550d41f,Šleh bičem +10,Move,439,name,4ff3d24e,Kamenné dělo +10,Move,440,name,d1dcfa83,Křížový jed +10,Move,441,name,ea40fa28,Zásah sajrajtem +10,Move,442,name,d209cc83,Železná hlava +10,Move,443,name,3c98de0a,Magnetická puma +10,Move,444,name,a32c50e2,Kamenné ostří +10,Move,445,name,846476fb,Okouzlení +10,Move,446,name,0e271d4e,Zákeřný kámen +10,Move,447,name,775e5e5f,Uzel z trávy +10,Move,448,name,b95e1be5,Žvanění +10,Move,449,name,f01ecdc8,Rozsudek +10,Move,450,name,646abcaf,Hmyzí kousanec +10,Move,451,name,a47d9474,Paprsek náboje +10,Move,452,name,6ab3d1bd,Dřevěné kladivo +10,Move,453,name,a76a89e5,Proud vody +10,Move,454,name,08e15d74,Příkaz k útoku +10,Move,455,name,ecc0548e,Příkaz k obraně +10,Move,456,name,dd2223ca,Příkaz vyléčit +10,Move,457,name,a496dea1,Drcení hlavou +10,Move,458,name,1c906270,Dvojitá rána +10,Move,459,name,fdcf9161,Řev času +10,Move,460,name,57f2ff21,Trhlina prostoru +10,Move,461,name,4ca66f49,Měsíční tanec +10,Move,462,name,e188b59f,Drtivý stisk +10,Move,463,name,e7909976,Bouře lávy +10,Move,464,name,f23e5ed3,Temná prázdnota +10,Move,465,name,8884982f,Semínková světlice +10,Move,466,name,40a07971,Zlověstný vítr +10,Move,467,name,52ae84bd,Potopení do stínů +10,MoveDamageClass,1,description,9625b27f,Nezraňuje +10,MoveDamageClass,1,name,650d6c1e,nezraňující +10,MoveDamageClass,2,description,becfcf89,"Fyzické zranění, řídí se Útokem a Obranou" +10,MoveDamageClass,2,name,d7293008,fyzické +10,MoveDamageClass,3,name,4c6b3fe3,speciální +10,MoveEffect,1,short_effect,23235a1b,"Normálně zraňuje, bez vedlejších účinků." +10,MoveEffect,2,effect,7d461b9f,[Uspí]{mechanic:sleep} protivníka. +10,MoveEffect,2,short_effect,fab4aadd,Uspí protivníka. +10,MoveEffect,3,short_effect,18144c52,Má $effect_chance% šanci otrávit protivníka. +10,MoveEffect,4,short_effect,fb3bd06d,Vyléčí uživatele o polovinu způsobeného zranění. +10,MoveEffect,6,short_effect,f946e377,Má $effect_chance% šanci zmrazit protivníka. +10,MoveEffect,7,short_effect,1c05fdea,Má $effect_chance% šanci paralyzovat protivníka. +10,MoveEffect,9,short_effect,1ae7a8fa,Funguje jen na spící protivníky. Vyléčí uživatele o polovinu způsobeného zranění. +10,MoveEffect,11,short_effect,2c4fbe05,Zvýší uživatelův Útok o jeden stupeň. +10,MoveEffect,12,short_effect,ed733b1a,Zvýší uživatelovu Obranu o jeden stupeň. +10,MoveEffect,14,short_effect,f78303d0,Zvýší uživatelův Speciální útok o jeden stupeň. +10,MoveEffect,17,short_effect,7f8c2786,Zvýší uživatelovu Mrštnost o jeden stupeň. +10,MoveEffect,18,short_effect,6a2ed6eb,Vždy se trefí. +10,MoveEffect,19,short_effect,70f0f273,Sníží protivníkův Útok o jeden stupeň. +10,MoveEffect,20,short_effect,5449505f,Sníží protivníkovu Obranu o jeden stupeň. +10,MoveEffect,21,short_effect,3007bcd6,Sníží protivníkovu Rychlost o jeden stupeň. +10,MoveEffect,24,short_effect,2b318251,Sníží protivníkovu Přesnost o jeden stupeň. +10,MoveEffect,25,short_effect,c6b64cc3,Sníží protivníkovu Mrštnost o jeden stupeň. +10,MoveEffect,28,short_effect,e014580f,"Zaútočí každé kolo, 2-3×; poté zmate uživatele." +10,MoveEffect,29,short_effect,4ff5bbff,Okamžitě ukončí zápas s divokým pokémonem. Trenéry donutí vyměnit pokémona. +10,MoveEffect,30,short_effect,e5e4180d,Zaútočí 2-5× za sebou. +10,MoveEffect,32,short_effect,b2aff3a6,"Má $effect_chance% šanci, že se protivník zalekne." +10,MoveEffect,34,effect,cc5f917e,[Těžce otráví]{mechanic:Bad poison} protivníka. +10,MoveEffect,34,short_effect,385d3c02,Vážně otráví protivníka; jed ubere každé kolo více zranění. +10,MoveEffect,35,short_effect,9c649109,Rozsype peníze v hodnotě 5× úrovně uživatele. +10,MoveEffect,36,short_effect,0feee880,Na pět kol sníží zranění ze speciálních útoků o 50%. +10,MoveEffect,38,short_effect,ccec7cfa,"Uživatel na dvě kola usne, čímž se úplně vyléčí." +10,MoveEffect,39,short_effect,f5e02086,Vyřadí protivníka na jeden zásah. +10,MoveEffect,40,short_effect,99da8cca,"První kolo se nabíjí, zaútočí až v druhém." +10,MoveEffect,44,short_effect,3f03a70f,Má vysokou šanci na kritický zásah. +10,MoveEffect,49,short_effect,ff46b231,Zraní uživatele za 1/4 způsobeného zranění. +10,MoveEffect,51,short_effect,b2c51959,Zvýší uživatelův Útok o dva stupně. +10,MoveEffect,52,short_effect,3f0c2829,Zvýší uživatelovu Obranu o dva stupně. +10,MoveEffect,53,short_effect,f1bc9e43,Zvýší uživateli Rychlost o dvě úrovně. +10,MoveEffect,54,short_effect,44a7f3bf,Zvýší uživatelův Speciální útok o dva stupně. +10,MoveEffect,55,short_effect,76930d8e,Zvýší uživatelovu Speciální obranu o dva stupně. +10,MoveEffect,59,short_effect,0bff721c,Sníží protivníkův Útok o dva stupně. +10,MoveEffect,60,short_effect,3903a75d,Sníží protivníkovu Obranu o dva stupně. +10,MoveEffect,61,short_effect,ad03d235,Sníží protivníkovi Obranu o dva stupně. +10,MoveEffect,63,short_effect,5ec14f8a,Sníží protivníkovu Speciální obranu o dva stupně. +10,MoveEffect,66,short_effect,a2bdb301,Na pět kol sníží zranění z fyzických útoků o 50%. +10,MoveEffect,67,effect,22a71d53,[Otráví]{mechanic:Poison} protivníka. +10,MoveEffect,67,short_effect,7b481e44,Otráví protivníka. +10,MoveEffect,69,short_effect,c981b610,Má $effect_chance% šanci snížit protivníkův Útok o jeden stupeň. +10,MoveEffect,70,short_effect,804b11f9,Má $effect_chance% šanci snížit protivníkovu Obranu o jeden stupeň. +10,MoveEffect,71,short_effect,2797d6a0,Má $effect_chance% šanci snížit protivníkovu Rychlost o jeden stupeň. +10,MoveEffect,72,short_effect,3be14593,Má $effect_chance% šanci snížit protivníkův Speciální útok o jeden stupeň. +10,MoveEffect,73,short_effect,f408a390,Má $effect_chance% šanci snížit protivníkovu Speciální obranu o jeden stupeň. +10,MoveEffect,74,short_effect,145086cd,Má $effect_chance% šanci snížit protivníkovu Přesnost o jeden stupeň. +10,MoveEffect,77,short_effect,11cd0067,Má $effect_chance% šanci zmást protivníka. +10,MoveEffect,79,short_effect,6a2ed6eb,Vždy se trefí. +10,MoveEffect,80,short_effect,05acc33b,"Převede 1/4 uživatelova HP do figurky, která ho chrání před dalším zraněním a změnami stavu, dokud se nerozbije." +10,MoveEffect,81,short_effect,d1a68b36,Uživatel se vzdá následujícího kola; musí dobít energii. +10,MoveEffect,82,short_effect,6639923c,"Pokud je uživatel po použití tohoto útoku zasažen, zvýší se mu Útok o jeden stupeň." +10,MoveEffect,83,short_effect,8792f265,Zkopíruje posledně použitý protivníkův útok. +10,MoveEffect,85,short_effect,e7aee025,"Zasadí do protivníka semínka, která mu každé kolo ubírají HP a léčí uživatele." +10,MoveEffect,88,effect,aca65f16,"Udělí zranění v HP rovné uživatelovi úrovni. Typové imunity platí, ale ostatní efekty typů jsou ignorovány." +10,MoveEffect,88,short_effect,ed35d2b8,Udělí zranění v HP rovné uživatelovi úrovni. +10,MoveEffect,90,short_effect,09ad6a50,"Způsobí dvakrát více fyzického zranění, než kolik utržil uživatel když byl naposledy zasažen." +10,MoveEffect,93,short_effect,3eed658f,"Má $effect_chance% šanci, že se protivník zalekne. Povede se pouze při spánku." +10,MoveEffect,98,short_effect,41aba78d,Použije náhodný z uživatelových ostatních útoků. Povede se pouze ze spánku. +10,MoveEffect,101,short_effect,6e3a19b0,Sníží PP protivníkova posledně použitého útoku o 4. +10,MoveEffect,104,short_effect,23235a1b,"Normálně zraňuje, bez vedlejších účinků." +10,MoveEffect,106,short_effect,e90bc79a,Vezme protivníkovi drženou věc. +10,MoveEffect,107,short_effect,97bb1dd8,Zabrání protivníkovi opustit zápas. +10,MoveEffect,109,short_effect,c627085a,Zvýší uživatelovu Mrštnost obranu o dva stupně. +10,MoveEffect,110,short_effect,1da483ee,"Duchové obětují 1/2 max. HP na kletbu, která protivníka zraňuje každé kolo. Ostatním sníží Rychlost a zvýší Útok a Obranu." +10,MoveEffect,112,short_effect,eaa0480b,Znemožní jakémukoli útoku trefit toto kolo uživatele. +10,MoveEffect,116,short_effect,c2ade64e,Na pět kol způsobí písečnou bouři. +10,MoveEffect,117,short_effect,9ff7f774,Uživateli toto kolo zůstane aspoň 1 HP. +10,MoveEffect,118,short_effect,a3c01065,"Kdy je tento útok používán stále dokola, jeho síla se prvních 5 kol vždy zdvojnásobí." +10,MoveEffect,119,short_effect,f15db692,"Zvýší protivníkův Útok o dva stupně, a zmate ho." +10,MoveEffect,120,short_effect,17cb9c2a,"Kdy je tento útok používán stále dokola, jeho síla se prvních 5 kol vždy zdvojnásobí; poté zůstane 32× silnější." +10,MoveEffect,121,short_effect,da8705cb,Protivník se zamiluje do uživatele. Poté má 50% šanci na něj nezaútočit. +10,MoveEffect,122,short_effect,275cec8d,"Tím silnější, čím je uživatel ochočenější. Maximální síla je 102." +10,MoveEffect,124,short_effect,95b64a67,"Tím silnější, čím je uživatel méně ochočený. Maximální síla je 102." +10,MoveEffect,125,short_effect,91592e71,Po pět kol chrání pokémony uživatelově straně proti změnám stavu a zmatení. +10,MoveEffect,133,short_effect,01e6d666,Uzdraví uživatele za 1/2 max. HP. Ovlivněno počasím. +10,MoveEffect,136,short_effect,fa633b41,Síla a typ závisí na uživatelových genech. Síla může být od 30 do 70. +10,MoveEffect,137,short_effect,55fadb74,Na pět kol způsobí déšť. +10,MoveEffect,138,short_effect,7b756cbf,"Na pět kol způsobí, že slunce svítí jasněji než normálně." +10,MoveEffect,141,short_effect,9c8a69f4,Má $effect_chance% šanci uživateli zvýšit všechny staty o jeden stupeň. +10,MoveEffect,145,short_effect,39dc96ff,"Způsobí dvakrát více speciálního zranění, než kolik utržil uživatel když byl naposledy zasažen." +10,MoveEffect,146,short_effect,580a3c8d,Zvýší uživateli Obranu o jeden stupeň. Zaútočí až v následujícím kole. +10,MoveEffect,147,short_effect,b2aff3a6,"Má $effect_chance% šanci, že se protivník zalekne." +10,MoveEffect,148,short_effect,f6e65697,Normálně zraňuje; zasáhne i pokémony pod zemí. +10,MoveEffect,151,short_effect,b2aff3a6,"Má $effect_chance% šanci, že se protivník zalekne." +10,MoveEffect,152,short_effect,99da8cca,"První kolo se nabíjí, zaútočí až v druhém." +10,MoveEffect,153,short_effect,1c05fdea,Má $effect_chance% šanci paralyzovat protivníka. +10,MoveEffect,157,short_effect,d1880f6b,Zvýší uživatelovu Obranu o jeden stupeň. +10,MoveEffect,160,short_effect,2b0a50fb,Používá se po několik kol stále dokola. Mezitím pokémoni nemohou usnout. +10,MoveEffect,165,short_effect,4ca65a6a,Na pět kol přivolá padající kroupy. +10,MoveEffect,169,short_effect,788358b7,Sníží protivníkův Útok a Speciální útok o dva stupně. Uživatel omdlí. +10,MoveEffect,170,short_effect,c746c692,"Při popálení, paralýze nebo otravě má dvojnásobnou sílu." +10,MoveEffect,171,short_effect,1fd4b377,"Pokud uživatel před zaútočením utrží zranění, tento útok se nepovede." +10,MoveEffect,174,short_effect,fb2ab93c,Použije útok závislý na okolním prostředí. +10,MoveEffect,175,short_effect,3112b936,Zvýší uživatelovu Speciální obranu o jeden stupeň. Jeho Elektrické útoky mají v příštím kole dvojnásobnou sílu. +10,MoveEffect,182,short_effect,6e143ad1,Nedovolí uživateli opustit zápas a léčí ho za 1/16 max. HP za kolo. +10,MoveEffect,183,short_effect,c386d647,Po zranění sníží uživateli Útok a Obranu o jeden stupeň. +10,MoveEffect,186,short_effect,e9a22d99,"Způsobí dvojnásobné zranění, pokud uživatel už byl toto kolo zraněn." +10,MoveEffect,187,short_effect,291b2bc1,Zruší Reflect a Light Screen. +10,MoveEffect,189,short_effect,4f944a38,Přinutí protivníka pustit drženou věc. +10,MoveEffect,190,short_effect,a4a2f496,"Sníží protivníkovo HP na tolik, kolik má uživatel." +10,MoveEffect,197,short_effect,3d4689ff,Silnější proti těžším protivníkům. Maximální síla je 120. +10,MoveEffect,198,short_effect,fd661e71,"Má $effect_chance% šanci způsobit změnu stavu. Jakou, to závisí na okolním prostředí." +10,MoveEffect,205,short_effect,f665d3c3,Po zranění protivníka sníží uživatelův Speciální útok o dva stupně. +10,MoveEffect,206,short_effect,8e074ef2,Sníží protivníkův Útok a Obranu o jeden stupeň. +10,MoveEffect,207,short_effect,9e392cc6,Zvýší uživatelovu Obranu a Speciální obranu o jeden stupeň. +10,MoveEffect,209,short_effect,72b98318,Zvýší uživatelův Útok a Obranu o jeden stupeň. +10,MoveEffect,212,short_effect,e93c08b4,Zvýší uživatelův Speciální útok a Speciální obranu o jeden stupeň. +10,MoveEffect,213,short_effect,0a2514af,Zvýší uživatelů Útok a Rychlost o jeden stupeň. +10,MoveEffect,219,short_effect,15640b6e,Sníží uživatelovu Rychlost o jeden stupeň. +10,MoveEffect,223,short_effect,08e89282,Síla a typ závisí na drženém ovoci. +10,MoveEffect,231,short_effect,f7776d92,"Má dvojnásobnou sílu, pokud protivník už toto kolo útočil." +10,MoveEffect,234,short_effect,05a5ddf4,Hodí na protivníka drženou věc; síla závisí na druhu věci. +10,MoveEffect,248,short_effect,12e97061,Změní protivníkovu schopnost na Insomnii. +10,MoveEffect,257,short_effect,2a86100e,"Uživatel se zahrabe pod zem, kde se vyhýbá všem útokům. V následujícím kole zaútočí." +10,MoveEffect,258,short_effect,3182f96f,Normálně zraňuje. Zasáhne i protivníky pod vodou. +10,MoveEffect,261,short_effect,f946e377,Má $effect_chance% šanci zmrazit protivníka. +10,MoveEffect,262,short_effect,ac2b7cc9,Po 2-5 kol nedovolí protivníkovi opustit zápas a uděluje mu zranění ve výši 1/16 max. HP za kolo. +10,MoveEffect,266,short_effect,5e85548e,"Sníží protivníkův Speciální útok o dva stupně, pokud je opačného pohlaví." +10,MoveEffect,267,short_effect,44faf89c,"Zraňuje protivníkovy pokémony, když jsou vysláni do zápasu." +10,MoveEffect,278,short_effect,c3afa6b1,Zvýší uživatelův Útok a Přesnost o jeden stupeň. +10,MoveEffect,284,short_effect,a1f99f7d,"Způsobí dvojnásobné zranění, pokud je protivník otávený." +10,MoveEffect,285,short_effect,ef1aa29f,Zvýší uživatelovu Rychlost o dva stupně a sníží mu hmotnost na polovinu. +10,MoveEffect,288,short_effect,1d348764,Zruší imunitu na Zemní typ. +10,MoveEffect,291,short_effect,b069d54b,"Zvýší uživatelův Speciální útok, Speciální obranu a Rychlost po jednom stupni." +10,MoveEffect,296,short_effect,0f5ce718,Nomálně zraňuje. Zvýší uživatelovu Rychlost o jeden stupeň. +10,MoveEffect,297,short_effect,5ec14f8a,Sníží protivníkovu Speciální obranu o dva stupně. +10,MoveEffect,302,short_effect,d6a8e3c0,"Má dvojnásobnou sílu, pokud se použije vícekrát v jednom kole." +10,MoveEffect,303,short_effect,e1e28672,"Síla se zvýší o 100% za každé po sobě jdoucí použití pokémonem na uživatelově straně, až do max. síly 200." +10,MoveEffect,304,short_effect,ae5be1c7,Ignoruje změny protivníkových statů. +10,MoveEffect,309,short_effect,4d7c9079,"Zvýší uživatelův Útok, Obranu a Rychlost o dva stupně. Sníží mu Obranu a Speciální obranu o jeden stupeň." +10,MoveEffect,313,short_effect,e56cd2f8,Zvýší uživateli Útok o jednu a Rychlost o dvě úrovně. +10,MoveEffect,314,short_effect,5e96b48c,Ukončí zápas s divokým pokémonem. Trenéry donutí vyměnit pokémona. +10,MoveEffect,315,short_effect,1eceb266,Zničí protivníkovi držené ovoce. +10,MoveEffect,317,short_effect,d0038269,Zvýší uživatelův Útok a Speciální útok o jeden stupeň. +10,MoveEffect,322,short_effect,900868ea,Zvýší uživatelův Speciální útok o tři stupně. +10,MoveEffect,323,short_effect,2de9c9c3,"Zvýší uživatelův Útok, Obranu a Přesnost útok po jednom stupni." +10,MoveEffect,328,short_effect,450f77e5,Zvýší uživatelův Útok a Speciální útok o jeden stupeň. +10,MoveEffect,329,short_effect,a6123581,Zvýší uživatelovu Obranu o tři stupně. +10,MoveEffect,331,short_effect,ad03d235,Sníží protivníkovi Obranu o dva stupně. +10,MoveEffect,332,short_effect,b167b80e,"První kolo se nabíjí, zaútočí až ve druhém. Má $effect_chance% šanci paralyzovat protivníka." +10,MoveEffect,338,short_effect,11cd0067,Má $effect_chance% šanci zmást protivníka. +10,MoveEffect,10004,short_effect,c028872e,Sníží protivníkovu Mrštnost o dva stupně. +10,MoveEffect,10006,short_effect,b2988a39,Na pět kol zatemní oblohu. +10,MoveFlagType,1,description,66523335,Uživatel se dotkne cíle. Toto zaktivuje některé schopnosti (např. [Static]{ability}) a věci (např. [Sticky Barb]{item}). +10,MoveFlagType,1,name,e34fed78,Způsobí dotek +10,MoveFlagType,2,description,d88ae74f,"Tento útok má nabíjecí kolo, které lze přeskočit pomocí [Power Herbu]{item:Power Herb}." +10,MoveFlagType,2,name,877244e9,Má nabíjecí kolo +10,MoveFlagType,3,description,6cea8671,"Kolo po použití tohoto útoku přeskočí Pokémon svůj tah, aby se mohl dobít." +10,MoveFlagType,3,name,80fd2238,Má dobíjecí kolo +10,MoveFlagType,4,description,102038da,"Tento útok nebude fungovat, použil-li cíl toto kolo [Detect]{move} či [Protect]{move}." +10,MoveFlagType,4,name,d249c071,Blokované Detectem a Protectem +10,MoveFlagType,5,name,b343310a,Odrazitelný +10,MoveFlagType,6,description,6d5f5422,"Tento útok bude ukraden, pokud jiný Pokémon toto kolo použil [Snatch]{move}" +10,MoveFlagType,6,name,3a15243a,Ukradnutelný +10,MoveFlagType,7,description,25e3b9d0,Pokémon co je cílem tohoto útoku ho může zkopírovat pomocí [Mirror Move]{move} +10,MoveFlagType,7,name,c4e8a756,Kopírovatelný Mirror Movem +10,MoveFlagType,8,name,ae7d93f1,Úderový +10,MoveFlagType,9,description,b34264c8,Pokémoni s [Soundproof]{ability} jsou proti tomuto útoku odolní. +10,MoveFlagType,9,name,752347d8,Zvukový +10,MoveFlagType,10,description,ee78fc04,Tento útok nelze použít při [zvýšené gravitaci]{move:Gravity} +10,MoveFlagType,10,name,05543fc0,Nepoužitelný s Gravitací +10,MoveFlagType,11,description,e68d87da,Tento útok může zmražený pokémon použít k rozmrznutí. +10,MoveFlagType,11,name,a2a771b1,Rozmrazí při použití +10,MoveFlagType,13,description,5660882a,Tento útok bude blokován [Heal Blockem]{move:Heal Block}. +10,MoveFlagType,13,name,eb08469e,Léčí +10,MoveMetaAilment,1,name,5134bb3b,Paralýza +10,MoveMetaAilment,2,name,cef2eda8,Spánek +10,MoveMetaAilment,6,name,4a28e9d1,Zmatení +10,MoveTarget,2,description,5a29a4b3,"Druhý Pokémon na bojišti, vybraný trenérem. + +Ukradené útoky použijí stejný cíl." +10,MoveTarget,2,name,af998c39,Vybraný Pokémon +10,MoveTarget,3,description,0418c304,Spolubojovník uživatele (je-li nějaký). +10,MoveTarget,3,name,a3b03f33,Spolubojovník +10,MoveTarget,4,description,7e0bf1a7,"Uživatelova strana bojiště. + +Působí na uživatele i jeho spolubojovníky (jsou-li nějací)" +10,MoveTarget,4,name,e51eed6c,Strana uživatele +10,MoveTarget,5,description,ca1b5030,"Buď uživatel nebo jeho spolubojovník, podle trenéra." +10,MoveTarget,5,name,70336963,Uživatel nebo spolubojovník +10,MoveTarget,6,description,b91b49aa,"Opačná strana bojiště. + +Působí na protivníky." +10,MoveTarget,6,name,bf5689eb,Strana protivnika +10,MoveTarget,7,description,d3e0a613,Působí na uživatele. +10,MoveTarget,7,name,2da17977,Uživatel +10,MoveTarget,8,description,8f851c2f,"Jeden protivník, vybraný náhodně." +10,MoveTarget,8,name,968b2389,Náhodný protivník +10,MoveTarget,9,description,fd9e4869,"Každý pokémon na poli, kromě uživatele." +10,MoveTarget,9,name,c1561b96,Všichni ostatní pokémoni +10,MoveTarget,10,description,2f56ae25,"Jeden pokémon na poli kromě uživatele, vybraný trenérem. " +10,MoveTarget,10,name,af998c39,Vybraný Pokémon +10,MoveTarget,11,description,1abe543e,Všichni protivníci. +10,MoveTarget,11,name,87082e08,Všichni protivníci +10,PokeathlonStat,2,name,6a4b2ea4,Síla +10,PokeathlonStat,4,name,f431f5e8,Výdrž +10,PokeathlonStat,5,name,0781a928,Skoky +10,Pokedex,1,description,12ca8f74,Celostvětový pokédex +10,Pokedex,1,name,698b64e6,Světové +10,Pokedex,2,description,9591a8ac,Pokédex z Kanta v Red/Blue/Yellow +10,Pokedex,2,name,be8c2832,Kanto +10,Pokedex,3,description,bf2d2b3e,Gold/Silver/Crystal Johto dex – ve hrách zvaný „New“ +10,Pokedex,3,name,9f1e2d3d,Původní Johto +10,Pokedex,4,description,a7653f5d,Ruby/Sapphire/Emerald Hoennský dex +10,Pokedex,4,name,d28c007d,Hoennský +10,Pokedex,5,description,dadaadab,Diamond/Pearl Sinnohský dex +10,Pokedex,6,description,f3d57c4c,Platinum Sinnohský dex—rozšířená verze dexu z Diamond a Pearl +10,Pokedex,7,description,56afcca4,HeartGold/SoulSilver Johto dex – dex z Gold/Silver/Crystal rozšířený o nové vývoje +10,Pokedex,8,description,79a9ade7,Black/White Unovský dex +10,Pokemon,1,flavor_summary,fe4d8c9a,"Od narození má na zádech divné semínko, které roste a vyvíjí se společně s ním. Uvnitř má zásobu živin, kterou využívají zejména malí Bulbasauři. I později ale může celé dny nejíst. + +Bulbasauři rádi odpočívají na sluníčku. Semínko na zádech jim tak roste rychleji." +10,Pokemon,1,species,e4b54c38,Semínkový +10,Pokemon,2,species,e4b54c38,Semínkový +10,Pokemon,3,species,e4b54c38,Semínkový +10,Pokemon,4,species,473b2152,Ještěrkovitý +10,Pokemon,5,species,c4ff3365,Plamenný +10,Pokemon,6,species,c4ff3365,Plamenný +10,Pokemon,7,species,a662e558,Malý želví +10,Pokemon,8,species,71b19d1a,Želví +10,Pokemon,9,species,4a927f13,Korýší +10,Pokemon,10,species,1a1f628b,Červí +10,Pokemon,11,species,011bd63a,Kuklový +10,Pokemon,12,species,4666d1bf,Motýlí +10,Pokemon,14,species,011bd63a,Kuklový +10,Pokemon,35,species,e1dde8d1,diblíci +10,Pokemon,36,species,e1dde8d1,diblíci +10,Pokemon,66,species,55017b02,Supersilný +10,Pokemon,67,species,55017b02,Supersilný +10,Pokemon,68,species,55017b02,Supersilný +10,Pokemon,88,species,63411a53,Kalový +10,Pokemon,89,species,63411a53,Kalový +10,Pokemon,96,species,834b9610,Hypnotický +10,Pokemon,97,species,834b9610,Hypnotický +10,Pokemon,111,species,11201819,Střepinový +10,Pokemon,120,species,a62f8434,Hvězdicovitý +10,Pokemon,121,species,abfc4744,Záhadný +10,Pokemon,122,species,2ef3c334,Bariérový +10,Pokemon,132,species,f543030e,Transformační +10,Pokemon,133,species,c0fdaa30,Vývoj +10,Pokemon,136,species,c4ff3365,Plamenný +10,Pokemon,140,species,4a927f13,Korýší +10,Pokemon,141,species,4a927f13,Korýší +10,Pokemon,144,species,413df12c,Zmražení +10,Pokemon,146,species,c4ff3365,Plamenný +10,Pokemon,173,species,a62f8434,Hvězdicovitý +10,Pokemon,191,species,e4b54c38,Semínkový +10,Pokemon,197,species,2227bb25,Měsíční +10,Pokemon,200,species,cd7e3dfb,Skřípavý +10,Pokemon,209,species,e1dde8d1,diblíci +10,Pokemon,210,species,e1dde8d1,diblíci +10,Pokemon,243,species,adc59c6c,Bleskový +10,Pokemon,261,species,21d49620,Kousavý +10,Pokemon,262,species,21d49620,Kousavý +10,Pokemon,265,species,1a1f628b,Červí +10,Pokemon,266,species,011bd63a,Kuklový +10,Pokemon,267,species,4666d1bf,Motýlí +10,Pokemon,268,species,011bd63a,Kuklový +10,Pokemon,277,species,211da6d8,Polykací +10,Pokemon,307,species,d2b08c9e,Meditační +10,Pokemon,308,species,d2b08c9e,Meditační +10,Pokemon,323,species,36a1520c,Erupční +10,Pokemon,325,species,8590f16f,Skákavý +10,Pokemon,351,species,836deaf2,Počasí +10,Pokemon,385,species,77e3dbf7,Přání +10,Pokemon,392,species,c4ff3365,Plamenný +10,Pokemon,403,species,6e0f7007,Jiskrový +10,Pokemon,404,species,5cd2e029,Jiskrový +10,Pokemon,466,species,f6d30876,Bleskový +10,Pokemon,523,species,f6d30876,Bleskový +10,Pokemon,613,species,eda9f31f,Studený +10,Pokemon,614,species,93e0b150,Mrazící +10,Pokemon,615,species,22733a94,Krystalizující +10,Pokemon,618,species,1ebdd00d,Pasťový +10,Pokemon,10013,species,836deaf2,Počasí +10,Pokemon,10014,species,836deaf2,Počasí +10,Pokemon,10015,species,836deaf2,Počasí +10,PokemonColor,4,name,25cd90d8,Šedá +10,PokemonForm,412,name,6ac22276,rostliny +10,PokemonForm,413,name,6ac22276,rostliny +10,PokemonForm,10077,name,f560b23a,Popálení +10,PokemonFormGroup,386,term,c82a81a2,Forma +10,PokemonFormGroup,412,term,4d8cf314,Plášť +10,PokemonFormGroup,413,term,4d8cf314,Plášť +10,PokemonFormGroup,487,term,c82a81a2,Forma +10,PokemonFormGroup,492,term,c82a81a2,Forma +10,PokemonFormGroup,648,term,c82a81a2,Forma +10,PokemonHabitat,1,name,057f6d41,jeskyně +10,PokemonHabitat,2,name,a85d53b4,lesy +10,PokemonHabitat,3,name,a73fc6cb,lučiny +10,PokemonHabitat,4,name,9da2093c,hory +10,PokemonHabitat,5,name,89b698bf,vzácní +10,PokemonHabitat,6,name,788350a9,vysočiny +10,PokemonHabitat,7,name,8bc9c137,moře +10,PokemonHabitat,8,name,ab99f942,města +10,PokemonHabitat,9,name,af51cb0b,břehy +10,PokemonMoveMethod,1,description,9d07ab12,Učení dosažením určité úrovně. +10,PokemonMoveMethod,1,name,05c78fbc,Úrovní +10,PokemonMoveMethod,2,description,a99cfa70,Dědí se od otce. +10,PokemonMoveMethod,2,name,707a9fa2,Vajíčkový +10,PokemonMoveMethod,3,description,e59d4a92,Učené nějakou postavou ve hře. +10,PokemonMoveMethod,3,name,58c6694c,Od učitele +10,PokemonMoveMethod,4,description,32164359,Učené použitím TM nebo HM +10,PokemonMoveMethod,4,name,dab8e618,Přístrojem +10,PokemonMoveMethod,5,name,769d65a7,Stadium: Surfující Pikachu +10,PokemonMoveMethod,6,description,12c34e57,"Objeví se u Pichu, jehož matka držela Light Ball. Otec nesmí být Ditto." +10,PokemonMoveMethod,6,name,7e51f60b,Light Ball +10,PokemonMoveMethod,7,description,77946fe2,"Objeví se u „Shadow“ pokémona, jak se mu otevírá srdce." +10,PokemonMoveMethod,7,name,4e0ea86f,Colosseum: Purifikace +10,PokemonMoveMethod,8,description,58d903a1,U ukradených „Shadow“ pokémonů +10,PokemonMoveMethod,8,name,d29fac50,XD: Stínový útok +10,PokemonMoveMethod,9,description,77946fe2,"Objeví se u „Shadow“ pokémona, jak se mu otevírá srdce." +10,PokemonMoveMethod,9,name,a5376e0d,XD: Purifikace +10,PokemonMoveMethod,10,description,5a7a0b0c,"Objeví se u Rotoma, když se změní do určité formy. Zmizí, když se Rotom změní do jiné formy, pokud se tento útok dá naučit jen změnou formy." +10,PokemonMoveMethod,10,name,2e006dfd,Forma Rotoma +10,PokemonShape,1,awesome_name,befc3d91,Kulatý +10,PokemonShape,2,awesome_name,b781c35b,Hadovitý +10,PokemonShape,2,name,24a0dafe,Hadovitý +10,PokemonShape,3,awesome_name,c46df7d0,Rybí +10,PokemonShape,4,awesome_name,e921524f,Dvouruký +10,PokemonShape,4,name,4cc66916,Dvouruký +10,PokemonShape,5,awesome_name,e6f03e37,Kaňka +10,PokemonShape,5,name,104d0af2,Kaňka +10,PokemonShape,6,awesome_name,5a8a075f,Vzpřímený +10,PokemonShape,6,name,954b96a6,Vzpřímený +10,PokemonShape,7,awesome_name,886af1cb,Dvounohý +10,PokemonShape,7,name,5d2aecb4,Dvounohý +10,PokemonShape,8,awesome_name,e67a6180,Čtyřnohý +10,PokemonShape,8,name,00636580,Čtyřnohý +10,PokemonShape,9,awesome_name,81cc98f6,Ptačí +10,PokemonShape,9,name,abc66783,Ptačí +10,PokemonShape,10,awesome_name,a23803dc,Ochapadlený +10,PokemonShape,10,name,070c5954,Ochapadlený +10,PokemonShape,11,awesome_name,9400d894,Složený +10,PokemonShape,11,name,23d1cc96,Složený +10,PokemonShape,12,awesome_name,15e2493f,Antropomorfní +10,PokemonShape,12,name,57a0c246,Antropomorfní +10,PokemonShape,13,awesome_name,77d644c2,Motýlovitý +10,PokemonShape,13,name,1745681c,Motýlovitý +10,PokemonShape,14,awesome_name,1b9162a7,Obrněný +10,PokemonShape,14,name,7ee6d1f8,Obrněný +10,Region,5,name,ed733435,Unovský +10,Stat,1,name,12128606,HP +10,Stat,2,name,406c280d,Útočný +10,Stat,3,name,1418cce9,Obranný +10,Stat,6,name,cee7d1f2,Rychlost +10,Type,1,name,26296dd2,Normální +10,Type,2,name,a3be6bed,Bojovný +10,Type,3,name,4d987ff5,Létající +10,Type,4,name,fed81c7f,Jedovatý +10,Type,5,name,a053865e,Zemní +10,Type,6,name,977b149c,Kamenný +10,Type,7,name,0dc1f9f4,Hmyzí +10,Type,8,name,c292bfa5,Duší +10,Type,9,name,4f5ece97,Ocelový +10,Type,10,name,f8eca6f1,Ohnivý +10,Type,11,name,3af23bde,Vodní +10,Type,12,name,69ccb0f7,Travní +10,Type,13,name,1d4ecdfc,Elektrický +10,Type,14,name,c81ee961,Psychický +10,Type,15,name,f302c2ee,Ledový +10,Type,16,name,20742c82,Dračí +10,Type,17,name,bb4e12c5,Temný +10,Type,10001,name,3ece7cbe,??? +10,Type,10002,name,72da871b,Stínový +10,Version,1,name,c22c196f,Červená +10,Version,2,name,3e04658a,Modrá +10,Version,3,name,d7a8e9a9,Žlutá +10,Version,4,name,e79bb26f,Zlatá +10,Version,5,name,28f71d6d,Stříbrná +10,Version,6,name,48372685,Krystalová +10,Version,7,name,6c687233,Rubínová +10,Version,8,name,f8ff20f3,Safírová +10,Version,9,name,655329f3,Smaragdová +10,Version,10,name,27cbb54c,OhnňoČervená +10,Version,11,name,62b1d74c,ListoZelená +10,Version,12,name,9de7ced0,Diamantová +10,Version,13,name,e2d1a8f7,Perlová +10,Version,14,name,d6c45e3b,Platinová +10,Version,15,name,baeedb89,SrdcoZlatá +10,Version,17,name,4844952c,Černá +10,Version,18,name,830ee2a0,Bílá From 9b7a3dc4c917981d49cb15f3bde24ee83793cdc2 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 7 Apr 2011 02:31:18 +0300 Subject: [PATCH 3/6] Make poupdate work correctly without a i18n directory --- bin/poupdate | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/bin/poupdate b/bin/poupdate index 06ccdf2..5a6fa17 100755 --- a/bin/poupdate +++ b/bin/poupdate @@ -318,6 +318,11 @@ if __name__ == '__main__': transl = translations.Translations.from_parsed_options(options) gettext_directory = options.gettext_directory + + if (options.pots or options.pos) and not os.path.exists(gettext_directory): + print "Error: Gettext directory doesn't exist. Skipping pot/po creation" + options.pots = options.pos = False + if options.pots: if options.verbose: print 'Creating pots in', gettext_directory @@ -337,27 +342,28 @@ if __name__ == '__main__': for lang, stream in csv_streams.items(): streams[lang].append(stream) - # Merge in the PO files - if options.langs: - langs = options.langs.split(',') - else: - langs = all_langs(gettext_directory) + if os.path.exists(gettext_directory): + # Merge in the PO files + if options.langs: + langs = options.langs.split(',') + else: + langs = all_langs(gettext_directory) - for lang in langs: - language_directory = os.path.join(gettext_directory, lang) - if options.verbose: - print 'Merging translations for %s in %s' % (lang, language_directory) - pos = merge_pos(transl, lang, language_directory) - - if options.pos: + for lang in langs: + language_directory = os.path.join(gettext_directory, lang) if options.verbose: - print 'Writing POs for %s' % lang - save_pos(pos, lang, gettext_directory=gettext_directory) + print 'Merging translations for %s in %s' % (lang, language_directory) + pos = merge_pos(transl, lang, language_directory) - if options.verbose: - print_stats(pos) + if options.pos: + if options.verbose: + print 'Writing POs for %s' % lang + save_pos(pos, lang, gettext_directory=gettext_directory) - streams[lang].append(yield_po_messages(pos)) + if options.verbose: + print_stats(pos) + + streams[lang].append(yield_po_messages(pos)) if options.csv: for lang, lang_streams in streams.items(): From 817c4c289d3746ad92d1f3ece741bbc851aa4409 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 3 Apr 2011 19:05:21 +0200 Subject: [PATCH 4/6] Don't dump unofficial translations in `pokedex dump` (Translations cannot be dumped properly because the source string hash isn't in the database.) By default, unofficial texts are only dumped for English, but that can be configured if someone wants CSVs for different language(s). Official texts (_names rows for official languages) are always dumped. --- pokedex/db/load.py | 56 ++++++++++++++++++++++++++++++++-------------- pokedex/main.py | 8 ++++++- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/pokedex/db/load.py b/pokedex/db/load.py index d307fd8..2b7c6ae 100644 --- a/pokedex/db/load.py +++ b/pokedex/db/load.py @@ -8,8 +8,8 @@ from sqlalchemy.orm.attributes import instrumentation_registry import sqlalchemy.sql.util import sqlalchemy.types -from pokedex.db import metadata -import pokedex.db.tables as tables +import pokedex +from pokedex.db import metadata, tables, translations from pokedex.defaults import get_default_csv_dir from pokedex.db.dependencies import find_dependent_tables @@ -306,7 +306,7 @@ def load(session, tables=[], directory=None, drop_tables=False, verbose=False, s -def dump(session, tables=[], directory=None, verbose=False): +def dump(session, tables=[], directory=None, verbose=False, langs=['en']): """Dumps the contents of a database to a set of CSV files. Probably not useful to anyone besides a developer. @@ -322,11 +322,15 @@ def dump(session, tables=[], directory=None, verbose=False): `verbose` If set to True, status messages will be printed to stdout. + + `langs` + List of identifiers of languages to dump unofficial texts for """ # First take care of verbosity print_start, print_status, print_done = _get_verbose_prints(verbose) + languages = dict((l.id, l) for l in session.query(pokedex.db.tables.Language)) if not directory: directory = get_default_csv_dir() @@ -342,25 +346,43 @@ def dump(session, tables=[], directory=None, verbose=False): writer = csv.writer(open("%s/%s.csv" % (directory, table_name), 'wb'), lineterminator='\n') columns = [col.name for col in table.columns] + + # For name tables, dump rows for official languages, as well as + # for those in `langs`. + # For other translation tables, only dump rows for languages in `langs` + # For non-translation tables, dump all rows. + if 'local_language_id' in columns: + if any(col.info.get('official') for col in table.columns): + def include_row(row): + return (languages[row.local_language_id].official or + languages[row.local_language_id].identifier in langs) + else: + def include_row(row): + return languages[row.local_language_id].identifier in langs + else: + def include_row(row): + return True + writer.writerow(columns) primary_key = table.primary_key for row in session.query(table).order_by(*primary_key).all(): - csvs = [] - for col in columns: - # Convert Pythony values to something more universal - val = getattr(row, col) - if val == None: - val = '' - elif val == True: - val = '1' - elif val == False: - val = '0' - else: - val = unicode(val).encode('utf-8') + if include_row(row): + csvs = [] + for col in columns: + # Convert Pythony values to something more universal + val = getattr(row, col) + if val == None: + val = '' + elif val == True: + val = '1' + elif val == False: + val = '0' + else: + val = unicode(val).encode('utf-8') - csvs.append(val) + csvs.append(val) - writer.writerow(csvs) + writer.writerow(csvs) print_done() diff --git a/pokedex/main.py b/pokedex/main.py index abd4597..5d6f70c 100644 --- a/pokedex/main.py +++ b/pokedex/main.py @@ -108,14 +108,20 @@ def get_csv_directory(options): def command_dump(*args): parser = get_parser(verbose=True) parser.add_option('-d', '--directory', dest='directory', default=None) + parser.add_option('-l', '--langs', dest='langs', default='en', + help="Comma-separated list of languages to dump all strings for. " + "Default is English ('en')") options, tables = parser.parse_args(list(args)) session = get_session(options) get_csv_directory(options) + langs = [l.strip() for l in options.langs.split(',')] + pokedex.db.load.dump(session, directory=options.directory, tables=tables, - verbose=options.verbose) + verbose=options.verbose, + langs=langs) def command_load(*args): parser = get_parser(verbose=True) From 34a80704493e0a29d154a0e55c8d5ad0ff698da1 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 3 Apr 2011 19:05:21 +0200 Subject: [PATCH 5/6] Load translations in pokedex load. --- pokedex/db/load.py | 22 +++++++++++++++++++++- pokedex/main.py | 13 ++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/pokedex/db/load.py b/pokedex/db/load.py index 2b7c6ae..c77064f 100644 --- a/pokedex/db/load.py +++ b/pokedex/db/load.py @@ -96,7 +96,7 @@ def _get_verbose_prints(verbose): return print_start, print_status, print_done -def load(session, tables=[], directory=None, drop_tables=False, verbose=False, safe=True, recursive=False): +def load(session, tables=[], directory=None, drop_tables=False, verbose=False, safe=True, recursive=True, langs=None): """Load data from CSV files into the given database session. Tables are created automatically. @@ -123,6 +123,9 @@ def load(session, tables=[], directory=None, drop_tables=False, verbose=False, s `recursive` If set to True, load all dependent tables too. + + `langs` + List of identifiers of extra language to load, or None to load them all """ # First take care of verbosity @@ -300,6 +303,23 @@ def load(session, tables=[], directory=None, drop_tables=False, verbose=False, s print_done() + + print_start('Translations') + transl = translations.Translations(csv_directory=directory) + + new_row_count = 0 + for translation_class, rows in transl.get_load_data(langs): + table_obj = translation_class.__table__ + if table_obj in table_objs: + insert_stmt = table_obj.insert() + session.connection().execute(insert_stmt, rows) + session.commit() + # We don't have a total, but at least show some increasing number + new_row_count += len(rows) + print_status(str(new_row_count)) + + print_done() + # SQLite check if session.connection().dialect.name == 'sqlite': session.connection().execute("PRAGMA integrity_check") diff --git a/pokedex/main.py b/pokedex/main.py index 5d6f70c..5ad63c0 100644 --- a/pokedex/main.py +++ b/pokedex/main.py @@ -130,6 +130,9 @@ def command_load(*args): parser.add_option('-r', '--recursive', dest='recursive', default=False, action='store_true') parser.add_option('-S', '--safe', dest='safe', default=False, action='store_true', help="Do not use backend-specific optimalizations.") + parser.add_option('-l', '--langs', dest='langs', default=None, + help="Comma-separated list of extra languages to load, or 'none' for none. " + "Default is to load 'em all. Example: 'fr,de'") options, tables = parser.parse_args(list(args)) if not options.engine_uri: @@ -139,6 +142,13 @@ def command_load(*args): print "`pokedex setup` to do both at once." print + if options.langs == 'none': + langs = [] + elif options.langs is None: + langs = None + else: + langs = [l.strip() for l in options.langs.split(',')] + session = get_session(options) get_csv_directory(options) @@ -147,7 +157,8 @@ def command_load(*args): tables=tables, verbose=options.verbose, safe=options.safe, - recursive=options.recursive) + recursive=options.recursive, + langs=langs) def command_reindex(*args): parser = get_parser(verbose=True) From 05cca043068462cdba3428fb1fab45b4ec54da2d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 3 Apr 2011 19:59:09 +0200 Subject: [PATCH 6/6] Add usage text for --langs --- pokedex/main.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pokedex/main.py b/pokedex/main.py index 5ad63c0..fe45577 100644 --- a/pokedex/main.py +++ b/pokedex/main.py @@ -301,6 +301,15 @@ Load options: -D|--drop-tables Drop all tables before loading data. -S|--safe Disable engine-specific optimizations. -r|--recursive Load (and drop) all dependent tables. + -l|--langs Load translations for the given languages. + By default, all available translations are loaded. + Separate multiple languages by a comma (-l en,de,fr) + +Dump options: + -l|--langs Dump unofficial texts for given languages. + By default, English (en) is dumped. + Separate multiple languages by a comma (-l en,de,fr) + Use 'none' to not dump any unofficial texts. Additionally, load and dump accept a list of table names (possibly with wildcards) and/or csv fileames as an argument list.