0

I have 2 files, origin.txt and end.txt

Origin.txt have text like this:

msgid "Page not found"
msgstr "Page not found translation"
msgid "Latest Listings"
msgstr "Latest Listings translation"

end.txt hasn't translations

msgid "Page not found"
msgstr ""
msgid "Latest Listings"
msgstr ""

I want to copy the translated words from origin.txt to end.txt. I decided to create a hash in ruby with all the words from origin.txt and then replace the translation between the "" in the line msgstr "". This is my current code:

tr = {}
msgidline = ""
msgstrline = ""

f = File.open("origin.txt", "r")

target = open("end.txt", 'r+')

# Create a dictionary 
f.each_line do |line|
    if line.start_with?("msgid", "msgstr") && line.start_with?(%Q(msgid "")) == false && line.start_with?(%Q(msgstr "")) == false
        if line.start_with?("msgid") == true
            msgidline = line.scan(/"([^"]*)"/).join()
        elsif line.start_with?("msgstr") == true
            msgstrline = line.scan(/"([^"]*)"/).join()
        end
    end
    tr[msgidline] = msgstrline
end

msgidlineReference = ""

target.each_line do |line|
    if line.start_with?("msgid", "msgstr") && line.start_with?(%Q(msgid "")) == false
        if line.start_with?("msgid") == true
            msgidReference = line.scan(/"([^"]*)"/).join()
        elsif line.start_with?("msgstr") == true 
            msgstrtext = line.scan(/"([^"]*)"/).join() 
            line.gsub(msgstrtext, tr[msgidReference])
        end
    end
end

target.close
f.close

I am getting a nil error:

translate.rb:29:in `gsub': no implicit conversion of nil into String (TypeError)

I don't know hot to make work the sub or gsub commands and replace the words in end.txt. This is the way to go? or do you think there is a better way to do it?

Edit update: I added ".join() at the end of scan. Now the nil error is gone. Thanks rantingsonrails. Now I need to write or replace in the file because it is not changing. Do you have any ideas?

coyr
  • 659
  • 6
  • 9
  • I'm surprised it runs without addressing `tr[msgidReference]` and changing to `tr[msgidReference].to_s`. With regard to writing to the file, you need to open end.txt with w+ instead of r+. – rantingsonrails Jan 19 '18 at 22:03

2 Answers2

2

Rewrote for more Rubyishness.

tr = {}
msgid = nil
File.readlines("origin.txt").each do |line|
  if (match = line[/^\s*msgid\s*"(.*)"\s*$/, 1])
    msgid = match
  elsif (match = line[/^\s*msgstr\s*"(.*)"\s*$/, 1])
    tr[msgid] = match
  end
end

lines = File.readlines("end.txt").map do |line|
  if (match = line[/^\s*msgid\s*"(.*)"\s*$/, 1])
    msgid = match
  elsif (match = line[/^\s*msgstr\s*"(.*)"\s*$/, 1])
    line[Regexp.last_match.begin(1), match.length] = tr[msgid]
  end
  line
end

File.write("end.txt", lines.join)

Note that you need to remember only the ID; then when you encounter the STR, you use both. In original code, you would end up with wrong tr because you put stuff into tr after each line - specifically after ID changes (but the associated STR hasn't been read yet). They need to be processed in pairs.

scan is an overkill, because it looks for multiple occurrences. There is only one occurrence we're interested in, per line; and String#[] is just so handy with Regexp.

It is typical in Ruby to use a block with open, so that we can guarantee the file handle is closed after it is not needed. Sure, f and target will be closed when the script exits, but it is not very elegant. However, we want lines, and there happens to exist a method that opens a file, gets all the lines and closes it - perfect tool for the job.

While we could open the file for reading and writing, read it then truncate it and write new contents, just opening it once for reading and once for writing is easier on the programmer.

As for lines, they could contain whitespace, or comments, or whatever that we're not interested; so each line that doesn't contain ID or STR is still kept, without change, while STR lines are transformed using String#[]= operator, to seamlessly slot in the new content exactly where we found the old.

Amadan
  • 191,408
  • 23
  • 240
  • 301
  • This code is wonderful, too much better than I was trying to accomplish. Thanks Amadan and rantingsonrails – coyr Jan 20 '18 at 00:16
  • It worked with a test "origin.txt" and "end.txt", but it is not working with the real file that has about 8000 lines. It is getting the following error: `code` translate-3.rb:15:in `[]=': no implicit conversion of nil into String (TypeError) from translate-3.rb:15:in `block in
    ' from translate-3.rb:11:in `map' from translate-3.rb:11:in `
    ' Any ideas?
    – coyr Jan 20 '18 at 00:39
  • I added .to_s at the end in `line[Regexp.last_match.begin(1), match.length] = tr[msgid].to_s`. It fixed this error. Now it doing it perfectly :D, thanks guys! – coyr Jan 20 '18 at 00:57
  • I got an encoding problem. I fixed adding at the beggining `# encoding: UTF-8` – coyr Jan 20 '18 at 01:30
0

The method scan returns an array, not a string, and gsub requires a string as a parameter. For example

If line = %q[msgid "Page not found"]

line.scan(/"([^"]*)"/) will return [["Page not found"]]

So something like the following would at least get rid of that error.

msgstrtext = line.scan(/"([^"]*)"/) 
unless msgstrtext.empty?
  line.gsub(msgstrtext.first.first, tr[msgidReference].to_s)
end

Full code including file writing and reading

tr = {}
msgidline = ""
msgstrline = ""

f = File.open("origin.txt", "r")

# Create a dictionary 
f.each_line do |line|
    if line.start_with?("msgid", "msgstr") && line.start_with?(%Q(msgid "")) == false && line.start_with?(%Q(msgstr "")) == false
        if line.start_with?("msgid") == true
            msgidline = line.scan(/"([^"]*)"/).join()
        elsif line.start_with?("msgstr") == true
            msgstrline = line.scan(/"([^"]*)"/).join()
        end
    end
    tr[msgidline] = msgstrline
end

@msgidlineReference = ""

new_lines = File.readlines("end.txt").map do |line|
    if line.start_with?("msgid") == true
      @msgidReference = line.scan(/"([^"]*)"/).join
      @new_line = line
    elsif line.start_with?("msgstr") == true 
      msgstrtext = line.scan(/"([^"]*)"/).join
      @new_line = %Q[msgstr "#{tr[@msgidReference].to_s}"\n]
    end
    @new_line
end

File.write("end.txt", new_lines.join)
rantingsonrails
  • 568
  • 4
  • 18
  • The error persist. May be I can use other commend instead of "scan" to get a string? – coyr Jan 19 '18 at 21:43
  • Error is now coming from `tr[msgidReference]`. Try replacing that with `tr[msgidReference].to_s` I've updated my example. – rantingsonrails Jan 19 '18 at 21:57
  • I added .join() at the end of scan, it fix the error. Is it a good option? – coyr Jan 19 '18 at 22:00
  • I tried to include storing the new lines and writing to end.txt in a way that was as close to your original code for readability purposes. Full code above. – rantingsonrails Jan 19 '18 at 22:46
  • Solution by @Amadan is far more elegant and quick. This was what I came up with trying to use your original code as much as possible. – rantingsonrails Jan 19 '18 at 22:56