tags translation

This commit is contained in:
nyne
2024-10-17 15:08:14 +08:00
parent e1e571052f
commit ae60c1aa2f
10 changed files with 34010 additions and 20 deletions

1
assets/tags.json Normal file

File diff suppressed because one or more lines are too long

33764
assets/tags_tw.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -294,6 +294,7 @@ class _ComicDescription extends StatelessWidget {
if (tags != null) { if (tags != null) {
tags!.removeWhere((element) => element.removeAllBlank == ""); tags!.removeWhere((element) => element.removeAllBlank == "");
} }
var enableTranslate = App.locale.languageCode == 'zh';
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
@@ -326,9 +327,8 @@ class _ComicDescription extends StatelessWidget {
crossAxisAlignment: WrapCrossAlignment.end, crossAxisAlignment: WrapCrossAlignment.end,
children: [ children: [
for (var s in tags!) for (var s in tags!)
Padding( Container(
padding: const EdgeInsets.fromLTRB(0, 0, 4, 3), margin: const EdgeInsets.fromLTRB(0, 0, 4, 3),
child: Container(
padding: const EdgeInsets.fromLTRB(3, 1, 3, 3), padding: const EdgeInsets.fromLTRB(3, 1, 3, 3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: s == "Unavailable" color: s == "Unavailable"
@@ -340,11 +340,10 @@ class _ComicDescription extends StatelessWidget {
const BorderRadius.all(Radius.circular(8)), const BorderRadius.all(Radius.circular(8)),
), ),
child: Text( child: Text(
s, enableTranslate ? TagsTranslation.translateTag(s) : s,
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
), ),
), ),
)
], ],
), ),
), ),
@@ -571,7 +570,6 @@ String? isBlocked(Comic item) {
return word; return word;
} }
} }
// TODO: check translated tags
} }
} }
return null; return null;

View File

@@ -26,6 +26,7 @@ import 'package:venera/network/cloudflare.dart';
import 'package:venera/pages/comic_page.dart'; import 'package:venera/pages/comic_page.dart';
import 'package:venera/pages/favorites/favorites_page.dart'; import 'package:venera/pages/favorites/favorites_page.dart';
import 'package:venera/utils/ext.dart'; import 'package:venera/utils/ext.dart';
import 'package:venera/utils/tags_translation.dart';
import 'package:venera/utils/translations.dart'; import 'package:venera/utils/translations.dart';
part 'image.dart'; part 'image.dart';

View File

@@ -199,6 +199,10 @@ class ComicSource {
final LinkHandler? linkHandler; final LinkHandler? linkHandler;
final bool enableTagsSuggestions;
final bool enableTagsTranslate;
Future<void> loadData() async { Future<void> loadData() async {
var file = File("${App.dataPath}/comic_source/$key.data"); var file = File("${App.dataPath}/comic_source/$key.data");
if (await file.exists()) { if (await file.exists()) {
@@ -264,6 +268,8 @@ class ComicSource {
this.translations, this.translations,
this.handleClickTagEvent, this.handleClickTagEvent,
this.linkHandler, this.linkHandler,
this.enableTagsSuggestions,
this.enableTagsTranslate,
); );
} }

View File

@@ -148,6 +148,8 @@ class ComicSourceParser {
_parseTranslation(), _parseTranslation(),
_parseClickTagEvent(), _parseClickTagEvent(),
_parseLinkHandler(), _parseLinkHandler(),
_getValue("search.enableTagsSuggestions") ?? false,
_getValue("comic.enableTagsTranslate") ?? false,
); );
await source.loadData(); await source.loadData();

View File

@@ -6,6 +6,7 @@ import 'package:venera/foundation/history.dart';
import 'package:venera/foundation/js_engine.dart'; import 'package:venera/foundation/js_engine.dart';
import 'package:venera/foundation/local.dart'; import 'package:venera/foundation/local.dart';
import 'package:venera/network/cookie_jar.dart'; import 'package:venera/network/cookie_jar.dart';
import 'package:venera/utils/tags_translation.dart';
import 'package:venera/utils/translations.dart'; import 'package:venera/utils/translations.dart';
import 'foundation/appdata.dart'; import 'foundation/appdata.dart';
@@ -20,5 +21,6 @@ Future<void> init() async {
await JsEngine().init(); await JsEngine().init();
await ComicSource.init(); await ComicSource.init();
await LocalManager().init(); await LocalManager().init();
await TagsTranslation.readData();
CacheManager(); CacheManager();
} }

View File

@@ -17,6 +17,7 @@ import 'package:venera/pages/favorites/favorites_page.dart';
import 'package:venera/pages/reader/reader.dart'; import 'package:venera/pages/reader/reader.dart';
import 'package:venera/pages/search_result_page.dart'; import 'package:venera/pages/search_result_page.dart';
import 'package:venera/utils/io.dart'; import 'package:venera/utils/io.dart';
import 'package:venera/utils/tags_translation.dart';
import 'package:venera/utils/translations.dart'; import 'package:venera/utils/translations.dart';
import 'dart:math' as math; import 'dart:math' as math;
@@ -341,6 +342,9 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
).paddingHorizontal(16).paddingBottom(8); ).paddingHorizontal(16).paddingBottom(8);
} }
bool enableTranslation =
App.locale.languageCode == 'zh' && comicSource.enableTagsTranslate;
return SliverToBoxAdapter( return SliverToBoxAdapter(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -353,7 +357,15 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
children: [ children: [
buildTag(text: e.key.ts(comicSource.key), isTitle: true), buildTag(text: e.key.ts(comicSource.key), isTitle: true),
for (var tag in e.value) for (var tag in e.value)
buildTag(text: tag, onTap: () => onTapTag(tag, e.key)), buildTag(
text: enableTranslation
? TagsTranslation.translationTagWithNamespace(
tag,
e.key.toLowerCase(),
)
: tag,
onTap: () => onTapTag(tag, e.key),
),
], ],
), ),
if (comic.uploader != null) if (comic.uploader != null)

View File

@@ -0,0 +1,202 @@
/*
数据来自于:
https://github.com/EhTagTranslation/Database/tree/master/database
繁体中文由 @NeKoOuO (https://github.com/NeKoOuO) 提供
*/
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:venera/foundation/app.dart';
import 'package:venera/utils/ext.dart';
extension TagsTranslation on String{
static final Map<String, Map<String, String>> _data = {};
static Future<void> readData() async{
if(App.locale.languageCode != "zh"){
return;
}
var fileName = App.locale.countryCode == 'TW'
? "assets/tags_tw.json"
: "assets/tags.json";
var data = await rootBundle.load(fileName);
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
const JsonDecoder().convert(const Utf8Decoder().convert(bytes)).forEach((key, value){
_data[key] = {};
value.forEach((key1, value1){
_data[key]?[key1] = value1;
});
});
}
static bool _haveNamespace(String key) {
return _data.containsKey(key);
}
/// 对tag进行处理后进行翻译: 代表'或'的分割符'|', namespace.
static String _translateTags(String tag){
if (tag.contains('|')) {
var splits = tag.split(' | ');
return enTagsTranslations[splits[0]]??enTagsTranslations[splits[1]]??tag;
} else if(tag.contains(':')) {
var splits = tag.split(':');
if(_haveNamespace(splits[0])) {
return translationTagWithNamespace(splits[1], splits[0]);
} else {
return tag;
}
} else {
return enTagsTranslations[tag]??tag;
}
}
/// translate tag's text to chinese
String get translateTagsToCN => _translateTags(this);
static String translateTag(String tag) {
if(tag.contains(':')) {
var [namespace, text] = tag.split(':');
return translationTagWithNamespace(text, namespace);
} else {
return tag.translateTagsToCN;
}
}
static String translationTagWithNamespace(String text, String namespace){
text = text.toLowerCase();
if(text != "reclass" && text.endsWith('s')){
text.replaceLast('s', '');
}
return switch(namespace){
"male" => maleTags[text] ?? text,
"female" => femaleTags[text] ?? text,
"mixed" => mixedTags[text] ?? text,
"other" => otherTags[text] ?? text,
"parody" => parodyTags[text] ?? text,
"character" => characterTranslations[text] ?? text,
"group" => groupTags[text] ?? text,
"cosplayer" => cosplayerTags[text] ?? text,
"reclass" => reclassTags[text] ?? text,
"language" => languageTranslations[text] ?? text,
"artist" => artistTags[text] ?? text,
_ => text.translateTagsToCN
};
}
String _categoryTextDynamic(String c){
if(App.locale.languageCode == "zh"){
return translateTagsCategoryToCN;
}else{
return this;
}
}
String get categoryTextDynamic => _categoryTextDynamic(this);
String get translateTagsCategoryToCN => tagsCategoryTranslations[this]??this;
get tagsCategoryTranslations => switch(App.locale.countryCode){
"CN" => tagsCategoryTranslationsCN,
"TW" => tagsCategoryTranslationsTW,
_ => tagsCategoryTranslationsCN
};
static const tagsCategoryTranslationsCN = {
"language": "语言",
"artist": "画师",
"male": "男性",
"female": "女性",
"mixed": "混合",
"other": "其它",
"parody": "原作",
"character": "角色",
"group": "团队",
"cosplayer": "Coser",
"reclass": "重新分类",
"Languages": "语言",
"Artists": "画师",
"Characters": "角色",
"Groups": "团队",
"Tags": "标签",
"Parodies": "原作",
"Categories": "分类",
"Time": "时间"
};
static const tagsCategoryTranslationsTW = {
"language": "語言",
"artist": "畫師",
"male": "男性",
"female": "女性",
"mixed": "混合",
"other": "其他",
"parody": "原作",
"character": "角色",
"group": "團隊",
"cosplayer": "Coser",
"reclass": "重新分類",
"Languages": "語言",
"Artists": "畫師",
"Characters": "角色",
"Groups": "團隊",
"Tags": "標籤",
"Parodies": "原作",
"Categories": "分類",
"Time": "時間"
};
static Map<String, String> get maleTags => _data["male"] ?? const {};
static Map<String, String> get femaleTags => _data["female"] ?? const {};
static Map<String, String> get languageTranslations => _data["language"] ?? const {};
static Map<String, String> get parodyTags => _data["parody"] ?? const {};
static Map<String, String> get characterTranslations => _data["character"] ?? const {};
static Map<String, String> get otherTags => _data["other"] ?? const {};
static Map<String, String> get mixedTags => _data["mixed"] ?? const {};
static Map<String, String> get characterTags => _data["character"] ?? const {};
static Map<String, String> get artistTags => _data["artist"] ?? const {};
static Map<String, String> get groupTags => _data["group"] ?? const {};
static Map<String, String> get cosplayerTags => _data["cosplayer"] ?? const {};
static Map<String, String> get reclassTags => _data["reclass"] ?? const {};
/// English to chinese translations
///
/// Not include artists and group
static MultipleMap<String, String> get enTagsTranslations => MultipleMap([
maleTags, femaleTags, languageTranslations, parodyTags, characterTranslations,
otherTags, mixedTags
]);
}
enum TranslationType{
female, male, mixed, language, other, group, artist, cosplayer, parody,
character, reclass
}
class MultipleMap<S, T>{
final List<Map<S, T>> maps;
MultipleMap(this.maps);
T? operator[](S key) {
for (var map in maps){
var value = map[key];
if(value != null){
return value;
}
}
return null;
}
}

View File

@@ -61,4 +61,6 @@ flutter:
- assets/translation.json - assets/translation.json
- assets/init.js - assets/init.js
- assets/app_icon.png - assets/app_icon.png
- assets/tags.json
- assets/tags_tw.json