mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
tags translation
This commit is contained in:
1
assets/tags.json
Normal file
1
assets/tags.json
Normal file
File diff suppressed because one or more lines are too long
33764
assets/tags_tw.json
Normal file
33764
assets/tags_tw.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||||
|
@@ -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';
|
||||||
|
@@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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();
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
@@ -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)
|
||||||
|
202
lib/utils/tags_translation.dart
Normal file
202
lib/utils/tags_translation.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user