Add wadoku dictionary type

Julien LepillerThu Jun 11 04:20:21+0200 2020

d84e60a

Add wadoku dictionary type

CHANGELOG.md

2323
2424
* Add a Help screen and two topics: kanji input by component and romaji input.
2525
* Add a button for direct help access on radical input.
26+
* Application can now download dictionaries from Wadoku.
2627
2728
### Translations
2829

README.md

3131
| [JMdict](https://www.edrdg.org/jmdict/edict_doc.html)      | EDRDG    | CC-BY-SA 3.0 | Provides the main search function |
3232
| [RadK](https://www.edrdg.org/krad/kradinf.html) | EDRDG | CC-BY-SA 3.0 | Provides kanji by radical lookup |
3333
| [KanjiDic](https://www.edrdg.org/wiki/index.php/KANJIDIC_Project) | EDRDG | CC-BY-SA 3.0 | Currently used only in combination with RadK |
34+
| [Wadoku](https://wadoku.de) | Wadoku | [Non-commercial license](https://www.wadoku.de/wiki/display/WAD/Wadoku.de-Daten+Lizenz) | Provides the main search function |
3435
3536
3637
Contributing

app/src/main/java/eu/lepiller/nani/dictionary/DictionaryFactory.java

9292
                            d = new RadicalDict(name,
9393
                                    chooseLanguage(synopsis),
9494
                                    chooseLanguage(description),
95-
                                    cacheDir,
96-
                                    url,
97-
                                    size,
98-
                                    entries,
99-
                                    sha256);
95+
                                    cacheDir, url, size, entries, sha256);
10096
                        } else if (type.compareTo("jmdict") == 0) {
10197
                            d = new JMDict(name,
10298
                                    chooseLanguage(synopsis),
10399
                                    chooseLanguage(description),
104-
                                    cacheDir,
105-
                                    url,
106-
                                    size,
107-
                                    entries,
108-
                                    sha256);
100+
                                    cacheDir, url, size, entries, sha256);
101+
                        } else if (type.compareTo("wadoku") == 0) {
102+
                            d = new WadokuResultDictionary(name,
103+
                                    chooseLanguage(synopsis),
104+
                                    chooseLanguage(description),
105+
                                    cacheDir, url, size, entries, sha256);
109106
                        } else {
110107
                            continue;
111108
                        }

197194
        HashMap<String, Result> results = new HashMap<>();
198195
199196
        for(Dictionary d: dictionaries) {
200-
            if (d instanceof JMDict && d.isDownloaded()) {
197+
            if (d instanceof ResultDictionary && d.isDownloaded()) {
201198
                available++;
202-
                ArrayList<Result> dr = ((JMDict) d).search(text);
199+
                ArrayList<Result> dr = ((ResultDictionary) d).search(text);
203200
                if(dr != null) {
204201
                    for(Result r: dr) {
205202
                        String k = r.getKanji();

app/src/main/java/eu/lepiller/nani/dictionary/FileDictionary.java

195195
        StringBuilder b = new StringBuilder();
196196
        ArrayList<Boolean> bits = new ArrayList<>();
197197
        String c = null;
198-
        JMDict.Huffman h = huffman;
198+
        ResultDictionary.Huffman h = huffman;
199199
        while(c == null || !c.isEmpty()) {
200-
            if(h instanceof JMDict.HuffmanValue) {
201-
                c = ((JMDict.HuffmanValue) h).character;
200+
            if(h instanceof ResultDictionary.HuffmanValue) {
201+
                c = ((ResultDictionary.HuffmanValue) h).character;
202202
                Log.d(TAG, "Huffman read: " + c);
203203
                b.append(c);
204204
                h = huffman;
205-
            } else if(h instanceof JMDict.HuffmanTree) {
205+
            } else if(h instanceof ResultDictionary.HuffmanTree) {
206206
                if(bits.isEmpty()) {
207207
                    byte by = file.readByte();
208208
                    Log.d(TAG, "Read byte for huffman: " + by);

214214
215215
                Boolean bo = bits.get(0);
216216
                bits.remove(0);
217-
                h = bo? ((JMDict.HuffmanTree) h).right: ((JMDict.HuffmanTree) h).left;
217+
                h = bo? ((ResultDictionary.HuffmanTree) h).right: ((ResultDictionary.HuffmanTree) h).left;
218218
            }
219219
        }
220220

app/src/main/java/eu/lepiller/nani/dictionary/JMDict.java

11
package eu.lepiller.nani.dictionary;
22
3-
import android.util.Log;
4-
53
import java.io.File;
6-
import java.io.FileNotFoundException;
7-
import java.io.IOException;
8-
import java.io.RandomAccessFile;
9-
import java.util.ArrayList;
10-
import java.util.Arrays;
114
125
import eu.lepiller.nani.R;
13-
import eu.lepiller.nani.result.Result;
14-
15-
public class JMDict extends FileDictionary {
16-
    final private static String TAG = "JMDICT";
17-
    private Huffman kanjiHuffman, readingHuffman, meaningHuffman;
186
19-
    JMDict(String name, String description, String fullDescription, File cacheDir, String url,
20-
           int fileSize, int entries, String hash) {
7+
public class JMDict extends ResultDictionary {
8+
    JMDict(String name, String description, String fullDescription, File cacheDir, String url, int fileSize, int entries, String hash) {
219
        super(name, description, fullDescription, cacheDir, url, fileSize, entries, hash);
2210
    }
2311

2513
    int getDrawableId() {
2614
        return R.drawable.ic_nani_edrdg;
2715
    }
28-
29-
    @Override
30-
    public void remove() {
31-
        super.remove();
32-
        kanjiHuffman = null;
33-
        readingHuffman = null;
34-
        meaningHuffman = null;
35-
    }
36-
37-
    private Result getValue(RandomAccessFile file, long pos) throws IOException {
38-
        file.seek(pos);
39-
        Log.d(TAG, "Getting value at " + pos);
40-
        ArrayList<String> kanjis = getHuffmanStringList(file, kanjiHuffman);
41-
42-
        Log.d(TAG, "Getting readings");
43-
        ArrayList<Result.Reading> readings = new ArrayList<>();
44-
        int reading_number = file.readShort();
45-
        Log.d(TAG, reading_number + " readings.");
46-
        for(int i=0; i<reading_number; i++) {
47-
            ArrayList<String> reading_kanjis = getStringList(file);
48-
            Log.d(TAG, "kanjis: " + reading_kanjis);
49-
            ArrayList<String> reading_infos = getStringList(file);
50-
            Log.d(TAG, "infos: " + reading_kanjis);
51-
            ArrayList<String> reading_readings = getHuffmanStringList(file, readingHuffman);
52-
            Result.Reading r = new Result.Reading(reading_kanjis, reading_infos, reading_readings);
53-
            readings.add(r);
54-
        }
55-
56-
        ArrayList<Result.Sense> senses = new ArrayList<>();
57-
        int meaning_number = file.readShort();
58-
        Log.d(TAG, meaning_number + " meanings.");
59-
        for(int i=0; i<meaning_number; i++) {
60-
            ArrayList<String> sense_references = getStringList(file);
61-
            ArrayList<String> sense_limits = getStringList(file);
62-
            ArrayList<String> sense_infos = getStringList(file);
63-
            ArrayList<Result.Source> sense_sources = new ArrayList<>();
64-
            int source_number = file.readShort();
65-
            for(int j=0; j<source_number; j++) {
66-
                ArrayList<String> source_content = getStringList(file);
67-
                boolean source_wasei = file.read() != 0;
68-
                String source_type = getString(file);
69-
                String source_language = getString(file);
70-
                sense_sources.add(new Result.Source(source_content, source_wasei, source_type, source_language));
71-
            }
72-
            ArrayList<Integer> sense_tags = getIntList(file);
73-
            ArrayList<String> sense_glosses = getHuffmanStringList(file, meaningHuffman);
74-
            String sense_language = getString(file);
75-
            senses.add(new Result.Sense(sense_references, sense_limits, sense_infos, sense_sources,
76-
                    sense_tags, sense_glosses, sense_language));
77-
        }
78-
        return new Result(kanjis, readings, senses);
79-
    }
80-
81-
    private ArrayList<Integer> getValues(RandomAccessFile file, long triePos) throws IOException {
82-
        file.seek(triePos);
83-
        Log.d(TAG, "Getting values");
84-
        int valuesLength = file.readShort();
85-
        ArrayList<Integer> results = new ArrayList<>();
86-
87-
        Log.d(TAG, "Number of values: " + valuesLength);
88-
        for(int i=0; i<valuesLength; i++) {
89-
            results.add(file.readInt());
90-
        }
91-
92-
        int transitionLength = file.readByte();
93-
        Log.d(TAG, "Number of transitions: " + transitionLength);
94-
        int[] others = new int[transitionLength];
95-
        for(int i=0; i<transitionLength; i++) {
96-
            file.skipBytes(1);
97-
            others[i] = file.readInt();
98-
        }
99-
100-
        for(int i=0; i<transitionLength; i++) {
101-
            results.addAll(getValues(file, others[i]));
102-
        }
103-
104-
        Log.d(TAG, "result size: " + results.size());
105-
        return results;
106-
    }
107-
108-
    private ArrayList<Integer> searchTrie(RandomAccessFile file, long triePos, byte[] txt) throws IOException {
109-
        Log.d(TAG, "searchTrie: " + triePos);
110-
        Log.d(TAG, "Remaining transitions: " + new String(txt, encoding));
111-
        file.seek(triePos);
112-
        Log.d(TAG, "pointer: " + file.getFilePointer());
113-
        if(txt.length == 0) {
114-
            return getValues(file, triePos);
115-
        } else {
116-
            int valuesLength = file.readShort();
117-
            Log.d(TAG, "number of values: " + valuesLength);
118-
            file.skipBytes(valuesLength * 4);
119-
120-
            int transitionLength = file.readByte();
121-
            Log.d(TAG, "number of transitions: " + transitionLength);
122-
123-
            for(int i = 0; i < transitionLength; i++) {
124-
                byte letter = file.readByte();
125-
                Log.d(TAG, "Possible transition " + letter + "; Expected transition: " + txt[0]);
126-
                if(letter == txt[0]) {
127-
                    Log.d(TAG, "Taking transition "+letter);
128-
                    byte[] ntxt = new byte[txt.length-1];
129-
                    System.arraycopy(txt, 1, ntxt, 0, txt.length-1);
130-
                    return searchTrie(file, file.readInt(), ntxt);
131-
                } else {
132-
                    file.skipBytes(4);
133-
                }
134-
            }
135-
136-
            return null;
137-
        }
138-
    }
139-
140-
    ArrayList<Result> search(String text) throws IncompatibleFormatException {
141-
        if (isDownloaded()) {
142-
            try {
143-
                RandomAccessFile file = new RandomAccessFile(getFile(), "r");
144-
                byte[] header = new byte[14];
145-
                int l = file.read(header);
146-
                if (l != header.length)
147-
                    return null;
148-
149-
                // Check file format version
150-
                if(!Arrays.equals(header, "NANI_JMDICT001".getBytes()))
151-
                    throw new IncompatibleFormatException(getName());
152-
153-
                byte[] search = text.getBytes();
154-
155-
                long kanjiTriePos = file.readInt();
156-
                long readingTriePos = file.readInt();
157-
                long meaningTriePos = file.readInt();
158-
159-
                Log.d(TAG, "Search in: " + getFile());
160-
                Log.d(TAG, "kanji: " + kanjiTriePos);
161-
                Log.d(TAG, "reading: " + readingTriePos);
162-
                Log.d(TAG, "meaning: " + meaningTriePos);
163-
164-
                kanjiHuffman = loadHuffman(file);
165-
                readingHuffman = loadHuffman(file);
166-
                meaningHuffman = loadHuffman(file);
167-
168-
                logHuffman(readingHuffman, new ArrayList<Boolean>());
169-
170-
                // Search in Japanese, by kanji and reading
171-
                ArrayList<Integer> results = searchTrie(file, kanjiTriePos, search);
172-
                ArrayList<Integer> readingResults = searchTrie(file, readingTriePos, search);
173-
                if(results != null && readingResults != null)
174-
                    results.addAll(readingResults);
175-
                else if (results == null)
176-
                    results = readingResults;
177-
178-
                // Search in other language, by meaning
179-
                if(results == null || results.isEmpty())
180-
                    results = searchTrie(file, meaningTriePos, search);
181-
182-
                if(results == null || results.isEmpty())
183-
                    return null;
184-
185-
                ArrayList<Result> r = new ArrayList<>();
186-
                ArrayList<Integer> uniqResults = new ArrayList<>();
187-
                for(Integer i: results) {
188-
                    if(!uniqResults.contains(i))
189-
                        uniqResults.add(i);
190-
                }
191-
192-
                int[] uniqResultsArray = new int[uniqResults.size()];
193-
                for(int i=0; i<uniqResults.size(); i++) {
194-
                    uniqResultsArray[i] = uniqResults.get(i);
195-
                }
196-
                Arrays.sort(uniqResultsArray);
197-
198-
                Log.d(TAG, uniqResults.toString());
199-
200-
                int num = 0;
201-
                for(int i: uniqResultsArray) {
202-
                    if(num > 10)
203-
                        break;
204-
                    num++;
205-
                    r.add(getValue(file, i));
206-
                }
207-
                return r;
208-
            } catch (FileNotFoundException e) {
209-
                e.printStackTrace();
210-
            } catch (IOException e) {
211-
                e.printStackTrace();
212-
            }
213-
        }
214-
        return null;
215-
    }
21616
}

app/src/main/java/eu/lepiller/nani/dictionary/ResultDictionary.java unknown status 1

1+
package eu.lepiller.nani.dictionary;
2+
3+
import android.util.Log;
4+
5+
import java.io.File;
6+
import java.io.FileNotFoundException;
7+
import java.io.IOException;
8+
import java.io.RandomAccessFile;
9+
import java.util.ArrayList;
10+
import java.util.Arrays;
11+
12+
import eu.lepiller.nani.result.Result;
13+
14+
abstract public class ResultDictionary extends FileDictionary {
15+
    final private static String TAG = "JMDICT";
16+
    private Huffman kanjiHuffman, readingHuffman, meaningHuffman;
17+
18+
    ResultDictionary(String name, String description, String fullDescription, File cacheDir, String url,
19+
                     int fileSize, int entries, String hash) {
20+
        super(name, description, fullDescription, cacheDir, url, fileSize, entries, hash);
21+
    }
22+
23+
    @Override
24+
    public void remove() {
25+
        super.remove();
26+
        kanjiHuffman = null;
27+
        readingHuffman = null;
28+
        meaningHuffman = null;
29+
    }
30+
31+
    private Result getValue(RandomAccessFile file, long pos) throws IOException {
32+
        file.seek(pos);
33+
        Log.d(TAG, "Getting value at " + pos);
34+
        ArrayList<String> kanjis = getHuffmanStringList(file, kanjiHuffman);
35+
36+
        Log.d(TAG, "Getting readings");
37+
        ArrayList<Result.Reading> readings = new ArrayList<>();
38+
        int reading_number = file.readShort();
39+
        Log.d(TAG, reading_number + " readings.");
40+
        for(int i=0; i<reading_number; i++) {
41+
            ArrayList<String> reading_kanjis = getStringList(file);
42+
            Log.d(TAG, "kanjis: " + reading_kanjis);
43+
            ArrayList<String> reading_infos = getStringList(file);
44+
            Log.d(TAG, "infos: " + reading_kanjis);
45+
            ArrayList<String> reading_readings = getHuffmanStringList(file, readingHuffman);
46+
            Result.Reading r = new Result.Reading(reading_kanjis, reading_infos, reading_readings);
47+
            readings.add(r);
48+
        }
49+
50+
        ArrayList<Result.Sense> senses = new ArrayList<>();
51+
        int meaning_number = file.readShort();
52+
        Log.d(TAG, meaning_number + " meanings.");
53+
        for(int i=0; i<meaning_number; i++) {
54+
            ArrayList<String> sense_references = getStringList(file);
55+
            ArrayList<String> sense_limits = getStringList(file);
56+
            ArrayList<String> sense_infos = getStringList(file);
57+
            ArrayList<Result.Source> sense_sources = new ArrayList<>();
58+
            int source_number = file.readShort();
59+
            for(int j=0; j<source_number; j++) {
60+
                ArrayList<String> source_content = getStringList(file);
61+
                boolean source_wasei = file.read() != 0;
62+
                String source_type = getString(file);
63+
                String source_language = getString(file);
64+
                sense_sources.add(new Result.Source(source_content, source_wasei, source_type, source_language));
65+
            }
66+
            ArrayList<Integer> sense_tags = getIntList(file);
67+
            ArrayList<String> sense_glosses = getHuffmanStringList(file, meaningHuffman);
68+
            String sense_language = getString(file);
69+
            senses.add(new Result.Sense(sense_references, sense_limits, sense_infos, sense_sources,
70+
                    sense_tags, sense_glosses, sense_language));
71+
        }
72+
        return new Result(kanjis, readings, senses);
73+
    }
74+
75+
    private ArrayList<Integer> getValues(RandomAccessFile file, long triePos) throws IOException {
76+
        file.seek(triePos);
77+
        Log.d(TAG, "Getting values");
78+
        int valuesLength = file.readShort();
79+
        ArrayList<Integer> results = new ArrayList<>();
80+
81+
        Log.d(TAG, "Number of values: " + valuesLength);
82+
        for(int i=0; i<valuesLength; i++) {
83+
            results.add(file.readInt());
84+
        }
85+
86+
        int transitionLength = file.readByte();
87+
        Log.d(TAG, "Number of transitions: " + transitionLength);
88+
        int[] others = new int[transitionLength];
89+
        for(int i=0; i<transitionLength; i++) {
90+
            file.skipBytes(1);
91+
            others[i] = file.readInt();
92+
        }
93+
94+
        for(int i=0; i<transitionLength; i++) {
95+
            results.addAll(getValues(file, others[i]));
96+
        }
97+
98+
        Log.d(TAG, "result size: " + results.size());
99+
        return results;
100+
    }
101+
102+
    private ArrayList<Integer> searchTrie(RandomAccessFile file, long triePos, byte[] txt) throws IOException {
103+
        Log.d(TAG, "searchTrie: " + triePos);
104+
        Log.d(TAG, "Remaining transitions: " + new String(txt, encoding));
105+
        file.seek(triePos);
106+
        Log.d(TAG, "pointer: " + file.getFilePointer());
107+
        if(txt.length == 0) {
108+
            return getValues(file, triePos);
109+
        } else {
110+
            int valuesLength = file.readShort();
111+
            Log.d(TAG, "number of values: " + valuesLength);
112+
            file.skipBytes(valuesLength * 4);
113+
114+
            int transitionLength = file.readByte();
115+
            Log.d(TAG, "number of transitions: " + transitionLength);
116+
117+
            for(int i = 0; i < transitionLength; i++) {
118+
                byte letter = file.readByte();
119+
                Log.d(TAG, "Possible transition " + letter + "; Expected transition: " + txt[0]);
120+
                if(letter == txt[0]) {
121+
                    Log.d(TAG, "Taking transition "+letter);
122+
                    byte[] ntxt = new byte[txt.length-1];
123+
                    System.arraycopy(txt, 1, ntxt, 0, txt.length-1);
124+
                    return searchTrie(file, file.readInt(), ntxt);
125+
                } else {
126+
                    file.skipBytes(4);
127+
                }
128+
            }
129+
130+
            return null;
131+
        }
132+
    }
133+
134+
    ArrayList<Result> search(String text) throws IncompatibleFormatException {
135+
        if (isDownloaded()) {
136+
            try {
137+
                RandomAccessFile file = new RandomAccessFile(getFile(), "r");
138+
                byte[] header = new byte[14];
139+
                int l = file.read(header);
140+
                if (l != header.length)
141+
                    return null;
142+
143+
                // Check file format version
144+
                if(!Arrays.equals(header, "NANI_JMDICT001".getBytes()))
145+
                    throw new IncompatibleFormatException(getName());
146+
147+
                byte[] search = text.getBytes();
148+
149+
                long kanjiTriePos = file.readInt();
150+
                long readingTriePos = file.readInt();
151+
                long meaningTriePos = file.readInt();
152+
153+
                Log.d(TAG, "Search in: " + getFile());
154+
                Log.d(TAG, "kanji: " + kanjiTriePos);
155+
                Log.d(TAG, "reading: " + readingTriePos);
156+
                Log.d(TAG, "meaning: " + meaningTriePos);
157+
158+
                kanjiHuffman = loadHuffman(file);
159+
                readingHuffman = loadHuffman(file);
160+
                meaningHuffman = loadHuffman(file);
161+
162+
                logHuffman(readingHuffman, new ArrayList<Boolean>());
163+
164+
                // Search in Japanese, by kanji and reading
165+
                ArrayList<Integer> results = searchTrie(file, kanjiTriePos, search);
166+
                ArrayList<Integer> readingResults = searchTrie(file, readingTriePos, search);
167+
                if(results != null && readingResults != null)
168+
                    results.addAll(readingResults);
169+
                else if (results == null)
170+
                    results = readingResults;
171+
172+
                // Search in other language, by meaning
173+
                if(results == null || results.isEmpty())
174+
                    results = searchTrie(file, meaningTriePos, search);
175+
176+
                if(results == null || results.isEmpty())
177+
                    return null;
178+
179+
                ArrayList<Result> r = new ArrayList<>();
180+
                ArrayList<Integer> uniqResults = new ArrayList<>();
181+
                for(Integer i: results) {
182+
                    if(!uniqResults.contains(i))
183+
                        uniqResults.add(i);
184+
                }
185+
186+
                int[] uniqResultsArray = new int[uniqResults.size()];
187+
                for(int i=0; i<uniqResults.size(); i++) {
188+
                    uniqResultsArray[i] = uniqResults.get(i);
189+
                }
190+
                Arrays.sort(uniqResultsArray);
191+
192+
                Log.d(TAG, uniqResults.toString());
193+
194+
                int num = 0;
195+
                for(int i: uniqResultsArray) {
196+
                    if(num > 10)
197+
                        break;
198+
                    num++;
199+
                    r.add(getValue(file, i));
200+
                }
201+
                return r;
202+
            } catch (FileNotFoundException e) {
203+
                e.printStackTrace();
204+
            } catch (IOException e) {
205+
                e.printStackTrace();
206+
            }
207+
        }
208+
        return null;
209+
    }
210+
}

app/src/main/java/eu/lepiller/nani/dictionary/WadokuResultDictionary.java unknown status 1

1+
package eu.lepiller.nani.dictionary;
2+
3+
import java.io.File;
4+
5+
import eu.lepiller.nani.R;
6+
7+
public class WadokuResultDictionary extends ResultDictionary {
8+
    WadokuResultDictionary(String name, String description, String fullDescription, File cacheDir, String url, int fileSize, int entries, String hash) {
9+
        super(name, description, fullDescription, cacheDir, url, fileSize, entries, hash);
10+
    }
11+
12+
    @Override
13+
    int getDrawableId() {
14+
        return R.drawable.wadoku;
15+
    }
16+
}

app/src/main/res/drawable/wadoku.png unknown status 1

Binary data

app/src/main/res/layout/activity_about.xml

212212
            app:lineHeight="22dp"
213213
            android:text="@string/kanjidic_license" />
214214
215+
216+
        <TextView
217+
            android:id="@+id/textView21"
218+
            android:layout_width="match_parent"
219+
            android:layout_height="wrap_content"
220+
            android:layout_marginTop="16dp"
221+
            android:text="@string/wadoku_title"
222+
            android:textColor="@color/design_default_color_primary_dark"
223+
            android:textSize="@dimen/subtitle_size" />
224+
225+
        <TextView
226+
            android:id="@+id/textView22"
227+
            android:layout_width="match_parent"
228+
            android:layout_height="wrap_content"
229+
            app:lineHeight="22dp"
230+
            android:text="@string/wadoku_descr" />
231+
232+
        <TextView
233+
            android:id="@+id/textView23"
234+
            android:layout_width="match_parent"
235+
            android:layout_height="wrap_content"
236+
            app:lineHeight="22dp"
237+
            android:text="@string/wadoku_license" />
238+
215239
    </LinearLayout>
216240
217241
</ScrollView>
217241=
218242=
\ No newline at end of file

app/src/main/res/values-fr/strings.xml

124124
        en japonais. Il couvre les 13 108 kanji des trois standards japonais principaux.
125125
    </string>
126126
    <string name="kanjidic_license">Cette source est disponible sous license Creative Commons Share-Alike.</string>
127+
    <string name="wadoku_title">Wadoku.de</string>
128+
    <string name="wadoku_descr">Le projet wadoku, cr???? en l\'an 2000, est un dictionnaire
129+
        japonais/allemand. Son but est de cr??er une liste de vocabulaire en priorisant les nouveaux
130+
        termes et les sujets de recherche. C\'est l\'un des dictionnaires japonais/allemand les plus
131+
        complets.</string>
132+
    <string name="wadoku_license">Ces donn??es sont disponibles gratuitement, sous une licence
133+
        non-commerciale. Nous l\'utilisons parce que cette appli est gratuite et n\'affiche pas
134+
        de publicit??. Nous avons appliqu?? des changements ?? la base de donn??es pour la convertir en
135+
        notre format interne, en accord avec la licence. Voir
136+
        https://www.wadoku.de/wiki/display/WAD/Wadoku.de-Daten+Lizenz pour le texte complet de
137+
        la licence.</string>
127138
128139
    <!-- Help Activities -->
129140
    <string name="help_intro">Bienvenue dans le centre d\'aide de Nani !</string>

app/src/main/res/values/strings.xml

127127
        covers the 13,108 kanji in three main Japanese standards.
128128
    </string>
129129
    <string name="kanjidic_license">This source is licensed under Creative Commons Share-Alike.</string>
130+
    <string name="wadoku_title">Wadoku.de</string>
131+
    <string name="wadoku_descr">The Wadoku project was created in 2000 as a free German/Japanese
132+
        dictionary. It goal is to create a vocabulary set with a focus on new terms and research
133+
        subjects. It is one of the most comprehensive German/Japanese dictionaries.</string>
134+
    <string name="wadoku_license">This data is available for free download, under a non-commercial
135+
        license.  We can use it because this app is free and does not display ads.  We have applied
136+
        changes to the database to convert it to our internal format, in accordance with the license
137+
        agreement.  See https://www.wadoku.de/wiki/display/WAD/Wadoku.de-Daten+Lizenz for the full
138+
        text of the license.</string>
130139
131140
    <!-- Help Activities -->
132141
    <string name="help_intro">Welcome to Nani\'s Help Center!</string>