smartdict-core 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +77 -0
- data/bin/smartdict +20 -0
- data/bin/smartdict-populator +50 -0
- data/config/default_config.yml +15 -0
- data/lib/smartdict/commands/abstract_command.rb +225 -0
- data/lib/smartdict/commands/has_format_list.rb +28 -0
- data/lib/smartdict/commands/help_command.rb +53 -0
- data/lib/smartdict/commands/list_command.rb +45 -0
- data/lib/smartdict/commands/translate_command.rb +35 -0
- data/lib/smartdict/commands.rb +10 -0
- data/lib/smartdict/core/command_manager.rb +27 -0
- data/lib/smartdict/core/driver_manager.rb +9 -0
- data/lib/smartdict/core/format_manager.rb +8 -0
- data/lib/smartdict/core/has_logger.rb +12 -0
- data/lib/smartdict/core/is_manager.rb +21 -0
- data/lib/smartdict/core/logger.rb +5 -0
- data/lib/smartdict/core/plugin_manager.rb +32 -0
- data/lib/smartdict/core.rb +12 -0
- data/lib/smartdict/drivers/abstract_driver.rb +61 -0
- data/lib/smartdict/drivers/google_translate_driver.rb +56 -0
- data/lib/smartdict/drivers.rb +6 -0
- data/lib/smartdict/errors.rb +11 -0
- data/lib/smartdict/formats/abstract_format.rb +21 -0
- data/lib/smartdict/formats/fb2_format.rb +40 -0
- data/lib/smartdict/formats/text_color_format.rb +52 -0
- data/lib/smartdict/formats/text_format.rb +9 -0
- data/lib/smartdict/formats.rb +8 -0
- data/lib/smartdict/list_builder.rb +51 -0
- data/lib/smartdict/models/driver.rb +14 -0
- data/lib/smartdict/models/language.rb +16 -0
- data/lib/smartdict/models/translated_word.rb +14 -0
- data/lib/smartdict/models/translation.rb +83 -0
- data/lib/smartdict/models/translation_query.rb +13 -0
- data/lib/smartdict/models/word.rb +13 -0
- data/lib/smartdict/models/word_class.rb +14 -0
- data/lib/smartdict/models.rb +11 -0
- data/lib/smartdict/plugin/initializer_context.rb +7 -0
- data/lib/smartdict/plugin.rb +9 -0
- data/lib/smartdict/runner.rb +5 -0
- data/lib/smartdict/storage/seeder.rb +32 -0
- data/lib/smartdict/storage/seeds/drivers.csv +2 -0
- data/lib/smartdict/storage/seeds/languages.csv +188 -0
- data/lib/smartdict/storage/seeds/word_classes.csv +10 -0
- data/lib/smartdict/storage.rb +44 -0
- data/lib/smartdict/translation.rb +19 -0
- data/lib/smartdict/translator.rb +38 -0
- data/lib/smartdict/version.rb +3 -0
- data/lib/smartdict-core.rb +1 -0
- data/lib/smartdict.rb +97 -0
- metadata +468 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
# Translation driver provides an ability to translate words using external
|
2
|
+
# data sources. It can be data from local disk, database or remote services
|
3
|
+
# like Google Translate.
|
4
|
+
# Every driver must inherit {Smartdict::Driver} and have implementation of
|
5
|
+
# {#translate} method. This method should sets translated and transcription
|
6
|
+
# properties. For examples you can see #{Smartdict::Driver::GoogleTranslateDriver}.
|
7
|
+
#
|
8
|
+
# In your implementation of {#translate} you need to use methods:
|
9
|
+
# * word - returns a word what needs to be translated.
|
10
|
+
# * from_lang - code of source language("en", "ru", etc)
|
11
|
+
# * to_lang - code of target language("en, "ru, etc)
|
12
|
+
#
|
13
|
+
# Usage:
|
14
|
+
# class HelloWorldDriver < Smartdict::Driver
|
15
|
+
# # Set name of driver, so DriverManager can identify it.
|
16
|
+
# name "hello_world"
|
17
|
+
#
|
18
|
+
# # This trivial example will always return the same translation.
|
19
|
+
# def translate
|
20
|
+
# self.translated = {"noun" => ["hello", "hi"], "verb" => ["greet"]}
|
21
|
+
# self.transcription = "he'leu"
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
class Smartdict::Drivers::AbstractDriver
|
25
|
+
|
26
|
+
attr_reader :word, :from_lang, :to_lang
|
27
|
+
attr_accessor :translated, :transcription
|
28
|
+
|
29
|
+
# Is used to identify a driver
|
30
|
+
cattr_accessor :name
|
31
|
+
|
32
|
+
def self.translate(*args)
|
33
|
+
self.new(*args).build_translation
|
34
|
+
end
|
35
|
+
|
36
|
+
# Sets driver name
|
37
|
+
def self.set_name(name)
|
38
|
+
self.name = name.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(word, from_lang, to_lang)
|
42
|
+
@word = word
|
43
|
+
@from_lang = from_lang
|
44
|
+
@to_lang = to_lang
|
45
|
+
translate
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_translation
|
49
|
+
translation = Smartdict::Translation.new(
|
50
|
+
:word => word,
|
51
|
+
:from_lang => from_lang.to_s,
|
52
|
+
:to_lang => to_lang.to_s,
|
53
|
+
:transcription => transcription,
|
54
|
+
:driver => self.name
|
55
|
+
)
|
56
|
+
translated.each do |word_class, words|
|
57
|
+
translation.translated[word_class] = words
|
58
|
+
end
|
59
|
+
translation
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Smartdict::Drivers
|
2
|
+
# The translation driver for Google Translate service.
|
3
|
+
class GoogleTranslateDriver < AbstractDriver
|
4
|
+
|
5
|
+
# Pretend being Firefox :)
|
6
|
+
USER_AGENT = "Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.1.16) Gecko/20110429 Iceweasel/3.5.16 (like Firefox/3.5.1623123)"
|
7
|
+
|
8
|
+
# Host of Google Translate service.
|
9
|
+
HOST = "translate.google.com"
|
10
|
+
|
11
|
+
set_name "google_translate"
|
12
|
+
|
13
|
+
# Sets translation and transcription. GoogleTranslate doesn't provide
|
14
|
+
# transcription, so it's nil.
|
15
|
+
def translate
|
16
|
+
self.translated = response_to_hash(get_response)
|
17
|
+
self.transcription = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Parses string return by GoogleTranslate
|
24
|
+
# Returns hash similar to this:
|
25
|
+
# { "noun" => ["try", "attempt"],
|
26
|
+
# "verb" => ["try", "offer"] }
|
27
|
+
# If no translation was found than returns nil.
|
28
|
+
# @return [Hash] every key is word class and every value is array of translated words.
|
29
|
+
def response_to_hash(response)
|
30
|
+
result = {}
|
31
|
+
array = YAML.load(response.gsub(/,+/, ', '))
|
32
|
+
array[1].each do |trans|
|
33
|
+
word_class = trans[0].empty? ? 'undefined' : trans[0]
|
34
|
+
result[word_class] = trans[1]
|
35
|
+
end
|
36
|
+
result
|
37
|
+
rescue NoMethodError
|
38
|
+
raise Smartdict::TranslationNotFound
|
39
|
+
end
|
40
|
+
|
41
|
+
# Sends remote request to GooogleTranslate service.
|
42
|
+
# @return [String] body of response.
|
43
|
+
def get_response
|
44
|
+
http = Net::HTTP.new(HOST, 80)
|
45
|
+
request = Net::HTTP::Get.new(http_path, {"User-Agent" => USER_AGENT})
|
46
|
+
http.request(request).read_body
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [String] http path for request to translate word.
|
50
|
+
def http_path
|
51
|
+
w = word.gsub(' ', '+')
|
52
|
+
"/translate_a/t?client=t&text=#{w}&hl=en&sl=#{from_lang}&tl=#{to_lang}&multires=1&otf=1&rom=1&ssel=0&tsel=0&sc=1"
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Smartdict::Formats::AbstractFormat
|
2
|
+
include Singleton
|
3
|
+
|
4
|
+
class_attribute :description
|
5
|
+
|
6
|
+
def self.set_description(description)
|
7
|
+
self.description = description
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param [Models::Translation] translation
|
11
|
+
# @return [String]
|
12
|
+
def self.format_translation(translation)
|
13
|
+
instance.format_translation(translation)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [Array] array of {Models::Translation}.
|
17
|
+
# @return [String]
|
18
|
+
def self.format_list(translations)
|
19
|
+
instance.format_list(translations)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'builder'
|
2
|
+
|
3
|
+
class Smartdict::Formats::Fb2Format < Smartdict::Formats::AbstractFormat
|
4
|
+
set_description "Displays translation in fiction book format."
|
5
|
+
|
6
|
+
|
7
|
+
def format_list(translations)
|
8
|
+
xml = Builder::XmlMarkup.new(:indent => 2)
|
9
|
+
xml.instruct!
|
10
|
+
xml.FictionBook(:xmlns => "http://www.gribuser.ru/xml/fictionbook/2.0") do |book|
|
11
|
+
book.description do |desc|
|
12
|
+
desc.tag!("document-info") do |doc_info|
|
13
|
+
doc_info.tag!("program-used", "Smartdict version #{Smartdict::VERSION}")
|
14
|
+
end
|
15
|
+
desc.tag!('title-info') do |title_info|
|
16
|
+
title_info.tag!('book-title', "#{Time.now.strftime('%F')} - English words")
|
17
|
+
title_info.genre 'sci_linguistic'
|
18
|
+
title_info.annotation do |annotation|
|
19
|
+
annotation.p "English words to learn"
|
20
|
+
annotation.p "The content generate by program Smardict v#{Smartdict::VERSION}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
book.body do |body|
|
25
|
+
body.title "TITLE"
|
26
|
+
translations.each do |translation|
|
27
|
+
body.section do |word_section|
|
28
|
+
word_section.title {|title| title.p "#{translation.word} [#{translation.transcription}]"}
|
29
|
+
translation.translated.each do |word_class, translations|
|
30
|
+
word_section.subtitle word_class
|
31
|
+
word_section.p translations.join("; ")
|
32
|
+
word_section.tag!("empty-line")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Smartdict::Formats::TextColorFormat < Smartdict::Formats::AbstractFormat
|
2
|
+
set_description "Displays translation with ASCII highlight"
|
3
|
+
|
4
|
+
# :nodoc:
|
5
|
+
def format_translation(translation)
|
6
|
+
result = "#{bold_green(translation.word)}"
|
7
|
+
result << green(" [#{translation.transcription}]") if translation.transcription
|
8
|
+
result << "\n"
|
9
|
+
|
10
|
+
translation.translated.each do |word_class, words|
|
11
|
+
result << " #{bold(word_class)}\n"
|
12
|
+
words.each do |word|
|
13
|
+
result << " #{word}\n"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
# :nodoc:
|
21
|
+
def format_list(translations)
|
22
|
+
result = ""
|
23
|
+
translations.each do |translation|
|
24
|
+
translated_words = translation.translated.values.map{|words| words.first(3)}.flatten
|
25
|
+
|
26
|
+
result << green(translation.word)
|
27
|
+
result << " - "
|
28
|
+
result << translated_words.join(", ")
|
29
|
+
result << "\n"
|
30
|
+
end
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def bold_green(text)
|
38
|
+
colorize(text, "1;32")
|
39
|
+
end
|
40
|
+
|
41
|
+
def bold(text)
|
42
|
+
colorize(text, 1)
|
43
|
+
end
|
44
|
+
|
45
|
+
def green(text)
|
46
|
+
colorize(text, 32)
|
47
|
+
end
|
48
|
+
|
49
|
+
def colorize(text, color_code)
|
50
|
+
"\e[#{color_code}m#{text}\e[0m"
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# TODO: tests
|
2
|
+
|
3
|
+
class Smartdict::ListBuilder
|
4
|
+
include Smartdict::Models
|
5
|
+
|
6
|
+
# Options:
|
7
|
+
# * +:since+
|
8
|
+
# * +:till+
|
9
|
+
# * +:from_lang+
|
10
|
+
# * +:to_lang+
|
11
|
+
# * +:driver+
|
12
|
+
#
|
13
|
+
# @return [Array] array of {Smartdict::Translation}.
|
14
|
+
def self.build(options = {})
|
15
|
+
new(options).send(:build)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def initialize(options)
|
22
|
+
@options = options
|
23
|
+
@options[:till] ||= Time.now
|
24
|
+
end
|
25
|
+
|
26
|
+
def build
|
27
|
+
fetch_translations.map(&:to_struct)
|
28
|
+
end
|
29
|
+
|
30
|
+
def fetch_translations
|
31
|
+
query = Translation.all(Translation.translation_queries.created_at.lte => @options[:till])
|
32
|
+
query = query.all(Translation.translation_queries.created_at.gte => @options[:since]) if @options[:since]
|
33
|
+
query = query.all(Translation.to_lang_id => to_lang.id) if to_lang
|
34
|
+
query = query.all(Translation.from_lang_id => from_lang.id) if from_lang
|
35
|
+
query = query.all(Translation.driver_id => driver.id) if driver
|
36
|
+
query
|
37
|
+
end
|
38
|
+
|
39
|
+
def from_lang
|
40
|
+
Language[@options[:from_lang]]
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_lang
|
44
|
+
Language[@options[:to_lang]]
|
45
|
+
end
|
46
|
+
|
47
|
+
def driver
|
48
|
+
Driver[@options[:driver]]
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Smartdict::Models::Driver
|
2
|
+
include DataMapper::Resource
|
3
|
+
include DataMapper::Validations
|
4
|
+
include DataMapper::Enum
|
5
|
+
|
6
|
+
acts_as_enumerated
|
7
|
+
|
8
|
+
property :id , Serial
|
9
|
+
property :name, String, :unique_index => true
|
10
|
+
|
11
|
+
has n, :translations
|
12
|
+
|
13
|
+
validates_presence_of :name
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Smartdict::Models::Language
|
2
|
+
include DataMapper::Resource
|
3
|
+
include DataMapper::Validations
|
4
|
+
include DataMapper::Enum
|
5
|
+
|
6
|
+
acts_as_enumerated :name_property => :code
|
7
|
+
|
8
|
+
property :id , Serial
|
9
|
+
property :name, String
|
10
|
+
property :code, String, :unique_index => true, :length => 2
|
11
|
+
|
12
|
+
has n, :words
|
13
|
+
|
14
|
+
validates_presence_of :name, :code
|
15
|
+
validates_uniqueness_of :name, :code
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Smartdict::Models::TranslatedWord
|
2
|
+
include DataMapper::Resource
|
3
|
+
|
4
|
+
property :id, Serial
|
5
|
+
property :translation_id, Integer, :unique_index => :index_translated_word
|
6
|
+
property :word_class_id , Integer, :unique_index => :index_translated_word
|
7
|
+
property :word_id , Integer, :unique_index => :index_translated_word
|
8
|
+
|
9
|
+
belongs_to :translation
|
10
|
+
belongs_to :word_class
|
11
|
+
belongs_to :word
|
12
|
+
|
13
|
+
validates_presence_of :translation_id, :word_class_id, :word_id
|
14
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
class Smartdict::Models::Translation
|
2
|
+
include DataMapper::Resource
|
3
|
+
include Smartdict::Models
|
4
|
+
|
5
|
+
property :id , Serial
|
6
|
+
property :word_id , Integer, :unique_index => :index_translation
|
7
|
+
property :from_lang_id, Integer, :unique_index => :index_translation
|
8
|
+
property :to_lang_id , Integer, :unique_index => :index_translation
|
9
|
+
property :driver_id , Integer, :unique_index => :index_translation
|
10
|
+
|
11
|
+
belongs_to :word
|
12
|
+
belongs_to :driver
|
13
|
+
belongs_to :from_lang, 'Language'
|
14
|
+
belongs_to :to_lang , 'Language'
|
15
|
+
has n, :translated_words
|
16
|
+
has n, :translation_queries
|
17
|
+
|
18
|
+
validates_presence_of :word
|
19
|
+
validates_presence_of :driver
|
20
|
+
validates_presence_of :from_lang_id
|
21
|
+
validates_presence_of :to_lang_id
|
22
|
+
|
23
|
+
|
24
|
+
# Create {Smartdict::Models::Translation} from {Smartdict::Translation}.
|
25
|
+
def self.create_from_struct(struct)
|
26
|
+
from_lang = Language[struct.from_lang]
|
27
|
+
to_lang = Language[struct.to_lang]
|
28
|
+
driver = Driver[struct.driver]
|
29
|
+
|
30
|
+
word = Word.first_or_create(:name => struct.word, :language_id => from_lang.id)
|
31
|
+
word.transcription = struct.transcription if word.transcription.blank?
|
32
|
+
word.save!
|
33
|
+
|
34
|
+
translation = self.create(
|
35
|
+
:word => word,
|
36
|
+
:driver => driver,
|
37
|
+
:from_lang_id => from_lang.id,
|
38
|
+
:to_lang_id => to_lang.id
|
39
|
+
)
|
40
|
+
|
41
|
+
struct.translated.each do |word_class_name, meanings|
|
42
|
+
meanings.each do |meaning|
|
43
|
+
w = Word.first_or_create(:name => meaning, :language_id => to_lang.id)
|
44
|
+
word_class = WordClass[word_class_name]
|
45
|
+
TranslatedWord.create(:word => w, :word_class => word_class, :translation => translation)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
translation
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.find(word, from_lang_code, to_lang_code, driver_name)
|
53
|
+
from_lang = Language[from_lang_code]
|
54
|
+
to_lang = Language[to_lang_code]
|
55
|
+
driver = Driver[driver_name]
|
56
|
+
|
57
|
+
word = Word.first(:name => word, :language_id => from_lang.id)
|
58
|
+
self.first(:from_lang => from_lang, :to_lang => to_lang, :word => word, :driver => driver)
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# TODO: it's a hack. Remove.
|
63
|
+
def initialize(*args)
|
64
|
+
self.class.finalize
|
65
|
+
self.word_class_id = 1
|
66
|
+
super(*args)
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_struct
|
70
|
+
struct = Smartdict::Translation.new(
|
71
|
+
:word => word.name,
|
72
|
+
:transcription => word.transcription,
|
73
|
+
:from_lang => from_lang.code,
|
74
|
+
:to_lang => to_lang.code,
|
75
|
+
:driver => driver.name
|
76
|
+
)
|
77
|
+
translated_words.each do |tw|
|
78
|
+
struct[tw.word_class.name] << tw.word.name
|
79
|
+
end
|
80
|
+
struct
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Smartdict::Models::TranslationQuery
|
2
|
+
include DataMapper::Resource
|
3
|
+
|
4
|
+
property :id , Serial
|
5
|
+
property :created_at , DateTime
|
6
|
+
property :translation_id, Integer
|
7
|
+
|
8
|
+
belongs_to :translation
|
9
|
+
|
10
|
+
before :save do
|
11
|
+
self.created_at = Time.now
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Smartdict::Models::Word
|
2
|
+
include DataMapper::Resource
|
3
|
+
|
4
|
+
property :id , Serial
|
5
|
+
property :name , String, :unique_index => :index_word
|
6
|
+
property :language_id , Integer, :unique_index => :index_word
|
7
|
+
property :transcription, String
|
8
|
+
|
9
|
+
belongs_to :language
|
10
|
+
has n, :translations
|
11
|
+
|
12
|
+
validates_presence_of :language_id, :name
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Word class is the same what type of speech is (noun, verb, etc.)
|
2
|
+
class Smartdict::Models::WordClass
|
3
|
+
include DataMapper::Resource
|
4
|
+
include DataMapper::Enum
|
5
|
+
|
6
|
+
acts_as_enumerated
|
7
|
+
|
8
|
+
property :id, Serial
|
9
|
+
property :name, String
|
10
|
+
|
11
|
+
has n, :translations
|
12
|
+
|
13
|
+
validates_presence_of :name
|
14
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Smartdict
|
2
|
+
module Models; end
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'smartdict/models/language'
|
6
|
+
require 'smartdict/models/translation'
|
7
|
+
require 'smartdict/models/translation_query'
|
8
|
+
require 'smartdict/models/word'
|
9
|
+
require 'smartdict/models/word_class'
|
10
|
+
require 'smartdict/models/translated_word'
|
11
|
+
require 'smartdict/models/driver'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Smartdict::Storage::Seeder
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def seed!
|
7
|
+
seed_files.each do |csv_file|
|
8
|
+
seed_from(csv_file)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def seed_files
|
16
|
+
seeds_dir = File.expand_path('../seeds', __FILE__)
|
17
|
+
Dir["#{seeds_dir}/*.csv"]
|
18
|
+
end
|
19
|
+
|
20
|
+
def seed_from(csv_file)
|
21
|
+
model = csv_file_to_model(csv_file)
|
22
|
+
CSV.read(csv_file, :headers => true).each do |row|
|
23
|
+
model.create(row.to_hash)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def csv_file_to_model(csv_file)
|
28
|
+
plural_name = File.basename(csv_file).gsub(/\.csv$/, "")
|
29
|
+
model_name = plural_name.classify
|
30
|
+
Smartdict::Models.const_get(model_name)
|
31
|
+
end
|
32
|
+
end
|