Albirew/nyaa-pantsu
Albirew
/
nyaa-pantsu
Archivé
1
0
Bifurcation 0

Country flags (language) for torrents. (#970)

* Add flags for torrents

Add a new field, .Language, to the Torrent model, and a new package,
torrentLanguages, which maps languages to flags. Added also a flag icon pack
from googlei18n/region-flags, with (mostly) public domain flags from Wikipedia.

* Optimize flags

* Use FlagSprites CSS instead of .png files

* Only use flags for languages we support

* Add test for CSS flags

Ensure that we have all the flags for the languages we support.

* Add AdditionalLanguages field to config

This allows us to support additional languages for new uploaded torrents,
even if we have no translation for it.

* Minor CSS fix

* Add "other" and "multiple" torrent languages

Also removed the TorrentLanguage struct, as it wasn't much useful.

* Fix test

* Add colspan=2 to category when language is empty

Also hide the language column if empty.

* Add lang field to search.

Hopefully it works with Elasticsearch as well, but I haven't tested
(lol Java)

* Add language field to ES index and settings

* Add language column to JS template

* Add keyword type to language ES field

* Remove 'raw' from keyword

* Set "simple" analyzer on language

* Document .Language field on Torrent model
Cette révision appartient à :
Ramon Dantas 2017-06-11 20:14:26 -03:00 révisé par ewhal
Parent f150f783e5
révision d8e17478f8
32 fichiers modifiés avec 512 ajouts et 10 suppressions

Voir le fichier

@ -168,5 +168,6 @@ type SearchParam struct {
UserID uint
Max uint
NotNull string
Language string
Query string
}

Voir le fichier

@ -34,6 +34,7 @@ type TorrentParam struct {
NotNull string // csv
Null string // csv
NameLike string // csv
Language string
}
// FromRequest : parse a request in torrent param
@ -90,6 +91,8 @@ func (p *TorrentParam) FromRequest(r *http.Request) {
ascending = true
}
language := strings.TrimSpace(r.URL.Query().Get("lang"))
p.NameLike = nameLike
p.Offset = uint32(pagenum)
p.Max = uint32(max)
@ -102,6 +105,7 @@ func (p *TorrentParam) FromRequest(r *http.Request) {
p.Status = status
p.Sort = sortMode
p.Category = category
p.Language = language
// FIXME 0 means no TorrentId defined
// Do we even need that ?
p.TorrentID = 0
@ -148,6 +152,10 @@ func (p *TorrentParam) ToFilterQuery() string {
query += " date: [* " + p.ToDate + "]"
}
if p.Language != "" {
query += " language:" + p.Language
}
return query
}
@ -230,5 +238,6 @@ func (p *TorrentParam) Clone() TorrentParam {
NotNull: p.NotNull,
Null: p.Null,
NameLike: p.NameLike,
Language: p.Language,
}
}

Voir le fichier

@ -85,6 +85,13 @@ torrents:
sukebei_categories: {"1_": "art", "1_1": "art_anime", "1_2": "art_doujinshi", "1_3": "art_games", "1_4": "art_manga", "1_5": "art_pictures", "2_": "real_life", "2_1": "real_life_photobooks_and_pictures", "2_2": "real_life_videos"}
# TorrentCleanCategories : Config for Site categories
clean_categories: {"3_": "anime", "3_12": "anime_amv", "3_5": "anime_english_translated", "3_13": "anime_non_english_translated", "3_6": "anime_raw", "2_": "audio", "2_3": "audio_lossless", "2_4": "audio_lossy", "4_": "literature", "4_7": "literature_english_translated", "4_8": "literature_raw", "4_14": "literature_non_english_translated", "5_": "live_action", "5_9": "live_action_english_translated", "5_10": "live_action_idol_pv", "5_18": "live_action_non_english_translated", "5_11": "live_action_raw", "6_": "pictures", "6_15": "pictures_graphics", "6_16": "pictures_photos", "1_": "software", "1_1": "software_applications", "1_2": "software_games"}
# EnglishOnlyCategories : Which categories will only accept English torrents
english_only_categories: ["3_5", "4_7", "5_9"]
# NonEnglishOnlyCategories : Which categories will only accept non-English torrents
non_english_only_categories: ["3_13", "4_14", "5_18"]
# AdditionalLanguages : Which languages are available for selection when uploading a torrent, aside from
# the ones we have translations.
additional_languages: ["es-mx"]
# TorrentFileStorage : Path to default torrent storage location (eg /var/www/wherever/you/want)
filestorage:
# TorrentStorageLink : Url of torrent file download location (eg https://your.site/somewhere/%s.torrent)

Voir le fichier

@ -105,6 +105,9 @@ type TorrentsConfig struct {
Status []bool `yaml:"status,omitempty,omitempty"`
SukebeiCategories map[string]string `yaml:"sukebei_categories,omitempty"`
CleanCategories map[string]string `yaml:"clean_categories,omitempty"`
EnglishOnlyCategories []string `yaml:"english_only_categories,omitempty"`
NonEnglishOnlyCategories []string `yaml:"non_english_only_categories,omitempty"`
AdditionalLanguages []string `yaml:"additional_languages,omitempty"`
FileStorage string `yaml:"filestorage,omitempty"`
StorageLink string `yaml:"storage_link,omitempty"`
CacheLink string `yaml:"cache_link,omitempty"`

Voir le fichier

@ -72,3 +72,6 @@ mappings:
type: long
filesize:
type: long
language:
type: text
analyzer: simple

Voir le fichier

@ -22,14 +22,14 @@ pgconn = psycopg2.connect(dbparams)
cur = pgconn.cursor()
cur.execute("""SELECT torrent_id, torrent_name, category, sub_category, status,
torrent_hash, date, uploader, downloads, filesize, seeders, leechers, completed
torrent_hash, date, uploader, downloads, filesize, seeders, leechers, completed, language
FROM {torrent_tablename}
WHERE deleted_at IS NULL""".format(torrent_tablename=torrent_tablename))
fetches = cur.fetchmany(CHUNK_SIZE)
while fetches:
actions = list()
for torrent_id, torrent_name, category, sub_category, status, torrent_hash, date, uploader, downloads, filesize, seeders, leechers, completed in fetches:
for torrent_id, torrent_name, category, sub_category, status, torrent_hash, date, uploader, downloads, filesize, seeders, leechers, completed, language in fetches:
doc = {
'id': torrent_id,
'name': torrent_name.decode('utf-8'),
@ -43,7 +43,8 @@ while fetches:
'filesize': filesize,
'seeders': seeders,
'leechers': leechers,
'completed': completed
'completed': completed,
'language': language
}
action = {
'_index': pantsu_index,

Voir le fichier

@ -39,10 +39,10 @@ while fetches:
if action == 'index':
select_cur = pgconn.cursor()
select_cur.execute("""SELECT torrent_id, torrent_name, category, sub_category, status,
torrent_hash, date, uploader, downloads, filesize, seeders, leechers, completed
torrent_hash, date, uploader, downloads, filesize, seeders, leechers, completed, language
FROM {torrent_tablename}
WHERE torrent_id = {torrent_id}""".format(torrent_id=torrent_id, torrent_tablename=torrent_tablename))
torrent_id, torrent_name, category, sub_category, status, torrent_hash, date, uploader, downloads, filesize, seeders, leechers, completed = select_cur.fetchone()
torrent_id, torrent_name, category, sub_category, status, torrent_hash, date, uploader, downloads, filesize, seeders, leechers, completed, language = select_cur.fetchone()
doc = {
'id': torrent_id,
'name': torrent_name.decode('utf-8'),
@ -56,7 +56,8 @@ while fetches:
'filesize': filesize,
'seeders': seeders,
'leechers': leechers,
'completed': completed
'completed': completed,
'language': language
}
new_action['_source'] = doc
select_cur.close()

Voir le fichier

@ -59,6 +59,8 @@ type Torrent struct {
WebsiteLink string `gorm:"column:website_link"`
AnidbID string `gorm:"column:anidb_id"`
Trackers string `gorm:"column:trackers"`
// Indicates the language of the torrent's content (eg. subs, dubs, raws, manga TLs)
Language string `gorm:"column:language"`
DeletedAt *time.Time
Uploader *User `gorm:"AssociationForeignKey:UploaderID;ForeignKey:user_id"`
@ -222,6 +224,7 @@ type TorrentJSON struct {
UploaderName template.HTML `json:"uploader_name"`
OldUploader template.HTML `json:"uploader_old"`
WebsiteLink template.URL `json:"website_link"`
Language string `json:"language"`
Magnet template.URL `json:"magnet"`
TorrentLink template.URL `json:"torrent"`
Seeders uint32 `json:"seeders"`
@ -272,6 +275,7 @@ func (t *TorrentJSON) ToTorrent() Torrent {
Leechers: t.Leechers,
Completed: t.Completed,
LastScrape: t.LastScrape,
Language: t.Language,
//FileList: TODO
}
return torrent
@ -349,6 +353,7 @@ func (t *Torrent) ToJSON() TorrentJSON {
UploaderName: util.SafeText(uploader),
OldUploader: util.SafeText(t.OldUploader),
WebsiteLink: util.Safe(t.WebsiteLink),
Language: t.Language,
Magnet: template.URL(magnet),
TorrentLink: util.Safe(torrentlink),
Leechers: t.Leechers,

Voir le fichier

@ -0,0 +1,8 @@
.flag-other {
background: url('flag_other.png') no-repeat;
}
.flag-multiple {
background: url('flag_multiple.png') no-repeat;
}

Fichier binaire non affiché.

Après

Largeur:  |  Hauteur:  |  Taille: 923 B

BIN
public/css/flags/flag_other.png Fichier normal

Fichier binaire non affiché.

Après

Largeur:  |  Hauteur:  |  Taille: 1.1 KiB

91
public/css/flags/flags.css Fichier normal
Voir le fichier

@ -0,0 +1,91 @@
/*!
* Generated with CSS Flag Sprite generator (https://www.flag-sprites.com/)
*/
.flag {
display: inline-block;
width: 32px;
height: 32px;
background: url('flags.png') no-repeat;
}
.flag.flag-ru {
background-position: 0 -96px;
}
.flag.flag-hu {
background-position: 0 -32px;
}
.flag.flag-nl {
background-position: -32px -64px;
}
.flag.flag-ro {
background-position: -128px -64px;
}
.flag.flag-se {
background-position: -32px -96px;
}
.flag.flag-th {
background-position: -64px -96px;
}
.flag.flag-pt {
background-position: -96px -64px;
}
.flag.flag-no {
background-position: -64px -64px;
}
.flag.flag-kr {
background-position: -128px -32px;
}
.flag.flag-mx {
background-position: 0 -64px;
}
.flag.flag-de {
background-position: -64px 0;
}
.flag.flag-tw {
background-position: -96px -96px;
}
.flag.flag-fr {
background-position: -128px 0;
}
.flag.flag-it {
background-position: -64px -32px;
}
.flag.flag-cn {
background-position: -32px 0;
}
.flag.flag-is {
background-position: -32px -32px;
}
.flag.flag-jp {
background-position: -96px -32px;
}
.flag.flag-br {
background-position: 0 0;
}
.flag.flag-us {
background-position: -128px -96px;
}
.flag.flag-es {
background-position: -96px 0;
}

3
public/css/flags/flags.min.css externe Fichier normal
Voir le fichier

@ -0,0 +1,3 @@
/*!
* Generated with CSS Flag Sprite generator (https://www.flag-sprites.com/)
*/.flag{display:inline-block;width:32px;height:32px;background:url('flags.png') no-repeat}.flag.flag-ru{background-position:0 -96px}.flag.flag-hu{background-position:0 -32px}.flag.flag-nl{background-position:-32px -64px}.flag.flag-ro{background-position:-128px -64px}.flag.flag-se{background-position:-32px -96px}.flag.flag-th{background-position:-64px -96px}.flag.flag-pt{background-position:-96px -64px}.flag.flag-no{background-position:-64px -64px}.flag.flag-kr{background-position:-128px -32px}.flag.flag-mx{background-position:0 -64px}.flag.flag-de{background-position:-64px 0}.flag.flag-tw{background-position:-96px -96px}.flag.flag-fr{background-position:-128px 0}.flag.flag-it{background-position:-64px -32px}.flag.flag-cn{background-position:-32px 0}.flag.flag-is{background-position:-32px -32px}.flag.flag-jp{background-position:-96px -32px}.flag.flag-br{background-position:0 0}.flag.flag-us{background-position:-128px -96px}.flag.flag-es{background-position:-96px 0}

BIN
public/css/flags/flags.png Fichier normal

Fichier binaire non affiché.

Après

Largeur:  |  Hauteur:  |  Taille: 10 KiB

Voir le fichier

@ -255,6 +255,8 @@ th, .home-td {
th { border-bottom-width: 2px; }
.tr-cat { width: 90px; text-align: center; }
.tr-lang { width: 32px; margin: 0; padding: 0; }
.tr-flag img { vertical-align: middle; }
.tr-name { width: auto; text-align: left; white-space: normal; word-break: break-word; font-weight: bold; }
.tr-links { width: 66px; }
.tr-cs { width: 5px; overflow: initial; text-overflow: initial; }

BIN
public/img/blank.gif Fichier normal

Fichier binaire non affiché.

Après

Largeur:  |  Hauteur:  |  Taille: 42 B

Voir le fichier

@ -45,4 +45,12 @@ function humanFileSize(bytes, si) {
var i = ~~(Math.log(bytes) / Math.log(k));
return i == 0 ? bytes + " B" : (bytes / Math.pow(k, i)).toFixed(1) + " " + "KMGTPEZY"[i - 1] + (si ? "" : "i") + "B";
}
// @license-end
function flagCode(language) {
split = language.split("-");
if (split.length > 1) {
return split[1];
}
return language;
}
// @license-end

Voir le fichier

@ -279,6 +279,7 @@ func TorrentEditModPanel(w http.ResponseWriter, r *http.Request) {
uploadForm.Hidden = torrent.Hidden
uploadForm.WebsiteLink = string(torrentJSON.WebsiteLink)
uploadForm.Description = string(torrentJSON.Description)
uploadForm.Language = torrent.Language
htv := formTemplateVariables{newPanelCommonVariables(r), uploadForm, messages.GetAllErrors(), messages.GetAllInfos()}
err := panelTorrentEd.ExecuteTemplate(w, "admin_index.html", htv)
log.CheckError(err)
@ -305,6 +306,7 @@ func TorrentPostEditModPanel(w http.ResponseWriter, r *http.Request) {
torrent.Hidden = uploadForm.Hidden
torrent.WebsiteLink = uploadForm.WebsiteLink
torrent.Description = uploadForm.Description
torrent.Language = uploadForm.Language
// torrent.Uploader = nil // GORM will create a new user otherwise (wtf?!)
db.ORM.Model(&torrent).UpdateColumn(&torrent)
messages.AddInfoT("infos", "torrent_updated")

Voir le fichier

@ -14,6 +14,7 @@ import (
"github.com/NyaaPantsu/nyaa/util/categories"
"github.com/NyaaPantsu/nyaa/util/filelist"
"github.com/NyaaPantsu/nyaa/util/publicSettings"
"github.com/NyaaPantsu/nyaa/util/torrentLanguages"
)
type captchaData struct {
@ -182,6 +183,25 @@ var FuncMap = template.FuncMap{
}
return ""
},
"GetTorrentLanguages": torrentLanguages.GetTorrentLanguages,
"LanguageName": func(code string, T publicSettings.TemplateTfunc) template.HTML {
if code == "other" || code == "multiple" {
return T("language_" + code + "_name")
}
if !torrentLanguages.LanguageExists(code) {
return T("unknown")
}
return T("language_" + code + "_name")
},
"FlagCode": func(languageCode string) string {
if languageCode == "other" || languageCode == "multiple" {
return languageCode
}
return torrentLanguages.FlagFromLanguage(languageCode)
},
"fileSize": func(filesize int64, T publicSettings.TemplateTfunc) template.HTML {
if filesize == 0 {
return T("unknown")

Voir le fichier

@ -76,7 +76,8 @@ func UploadPostHandler(w http.ResponseWriter, r *http.Request) {
Filesize: uploadForm.Filesize,
Description: uploadForm.Description,
WebsiteLink: uploadForm.WebsiteLink,
UploaderID: user.ID}
UploaderID: user.ID,
Language: uploadForm.Language}
torrent.ParseTrackers(uploadForm.Trackers)
db.ORM.Create(&torrent)

Voir le fichier

@ -181,6 +181,7 @@ func TorrentEditUserPanel(w http.ResponseWriter, r *http.Request) {
uploadForm.WebsiteLink = string(torrent.WebsiteLink)
uploadForm.Description = string(torrent.Description)
uploadForm.Hidden = torrent.Hidden
uploadForm.Language = torrent.Language
htv := formTemplateVariables{newCommonVariables(r), uploadForm, messages.GetAllErrors(), messages.GetAllInfos()}
err := userTorrentEd.ExecuteTemplate(w, "index.html", htv)
log.CheckError(err)
@ -217,6 +218,7 @@ func TorrentPostEditUserPanel(w http.ResponseWriter, r *http.Request) {
torrent.Hidden = uploadForm.Hidden
torrent.WebsiteLink = uploadForm.WebsiteLink
torrent.Description = uploadForm.Description
torrent.Language = uploadForm.Language
// torrent.Uploader = nil // GORM will create a new user otherwise (wtf?!)
db.ORM.Model(&torrent).UpdateColumn(&torrent)
messages.AddInfoT("infos", "torrent_updated")

Voir le fichier

@ -25,6 +25,7 @@ import (
"github.com/NyaaPantsu/nyaa/util"
"github.com/NyaaPantsu/nyaa/util/categories"
"github.com/NyaaPantsu/nyaa/util/metainfo"
"github.com/NyaaPantsu/nyaa/util/torrentLanguages"
"github.com/zeebo/bencode"
)
@ -38,6 +39,7 @@ const uploadFormDescription = "desc"
const uploadFormWebsiteLink = "website_link"
const uploadFormStatus = "status"
const uploadFormHidden = "hidden"
const uploadFormLanguage = "language"
// error indicating that you can't send both a magnet link and torrent
var errTorrentPlusMagnet = errors.New("Upload either a torrent file or magnet link, not both")
@ -60,6 +62,15 @@ var errInvalidWebsiteLink = errors.New("Website url or IRC link is invalid")
// error indicating a torrent's category is invalid
var errInvalidTorrentCategory = errors.New("Torrent category is invalid")
// error indicating a torrent's language is invalid
var errInvalidTorrentLanguage = errors.New("Torrent language is invalid")
// error indicating that a non-english torrent was uploaded to a english category
var errNonEnglishLanguageInEnglishCategory = errors.New("Torrent's category is for English translations, but torrent language isn't English.")
// error indicating that a english torrent was uploaded to a non-english category
var errEnglishLanguageInNonEnglishCategory = errors.New("Torrent's category is for non-English translations, but torrent language is English.")
type torrentsQuery struct {
Category int `json:"category"`
SubCategory int `json:"sub_category"`
@ -95,6 +106,7 @@ type TorrentRequest struct {
CaptchaID string `json:"-"`
WebsiteLink string `json:"website_link,omitempty"`
SubCategory int `json:"sub_category,omitempty"`
Language string `json:"language,omitempty"`
Infohash string `json:"hash,omitempty"`
CategoryID int `json:"-"`
@ -232,6 +244,11 @@ func (r *TorrentRequest) ExtractEditInfo(req *http.Request) error {
return err
}
err = r.ExtractLanguage(req)
if err != nil {
return err
}
return nil
}
@ -261,6 +278,48 @@ func (r *TorrentRequest) ExtractCategory(req *http.Request) error {
return nil
}
// ExtractLanguage : takes a http request, computes the torrent language from the form.
func (r *TorrentRequest) ExtractLanguage(req *http.Request) error {
isEnglishCategory := false
for _, cat := range config.Conf.Torrents.EnglishOnlyCategories {
if cat == r.Category {
isEnglishCategory = true
break
}
}
if r.Language == "other" || r.Language == "multiple" {
// In this case, only check if it's on a English-only category.
if isEnglishCategory {
return errNonEnglishLanguageInEnglishCategory
}
return nil
}
if r.Language == "" && isEnglishCategory { // If no language, but in an English category, set to en-us.
// FIXME Maybe this shouldn't be hard-coded?
r.Language = "en-us"
}
if !torrentLanguages.LanguageExists(r.Language) {
return errInvalidTorrentLanguage
}
if !strings.HasPrefix(r.Language, "en") {
if isEnglishCategory {
return errNonEnglishLanguageInEnglishCategory
}
} else {
for _, cat := range config.Conf.Torrents.NonEnglishOnlyCategories {
if cat == r.Category {
return errEnglishLanguageInNonEnglishCategory
}
}
}
return nil
}
// ExtractBasicValue : takes an http request and computes all basic fields for this form
func (r *TorrentRequest) ExtractBasicValue(req *http.Request) error {
if strings.HasPrefix(req.Header.Get("Content-type"), "multipart/form-data") || req.Header.Get("Content-Type") == "application/x-www-form-urlencoded" { // Multipart
@ -278,6 +337,7 @@ func (r *TorrentRequest) ExtractBasicValue(req *http.Request) error {
r.Status, _ = strconv.Atoi(req.FormValue(uploadFormStatus))
r.Remake = req.FormValue(uploadFormRemake) == "on"
r.Magnet = req.FormValue(uploadFormMagnet)
r.Language = req.FormValue(uploadFormLanguage)
} else { // JSON (no file upload then)
decoder := json.NewDecoder(req.Body)
err := decoder.Decode(&r)
@ -323,6 +383,11 @@ func (r *TorrentRequest) ExtractInfo(req *http.Request) error {
return err
}
err = r.ExtractLanguage(req)
if err != nil {
return err
}
tfile, err := r.ValidateMultipartUpload(req)
if err != nil {
return err

Voir le fichier

@ -24,6 +24,19 @@
{{ end }}
</select>
</div>
<div class="form-group">
<label for="language">{{ call $.T "torrent_language" }}</label>
<select name="language" id="language" class="form-input up-input" required>
<option value="">{{call $.T "select_a_torrent_language"}}</option>
<option value="other" {{if eq $.Form.Language "other"}}selected{{end}}>{{call $.T "language_other_name"}}</option>
<option value="multiple" {{if eq $.Form.Language "multiple"}}selected{{end}}>{{call $.T "language_multiple_name"}}</option>
{{ range $_, $language := (GetTorrentLanguages) }}
<option value="{{ $language }}" {{if eq $.Form.Language $language}}selected{{end}}>
{{LanguageName $language $.T}}
</option>
{{ end }}
</select>
</div>
<div class="form-group">
<label for="Status">{{call $.T "torrent_status"}}</label>
<select name="status" class="form-input up-input">

Voir le fichier

@ -1,5 +1,9 @@
{{define "title"}}{{call $.T "home"}}{{end}}
{{define "contclass"}}cont-home{{end}}
{{define "additional_header"}}
<link rel="stylesheet" href="/css/flags/flags.min.css">
<link rel="stylesheet" href="/css/flags/custom_flags.css">
{{end}}
{{define "content"}}
<!-- Contain the table within a grid, as for better sizing -->
<div class="results box">
@ -10,6 +14,7 @@
<th class="tr-cb"><input type="checkbox" name="select_all" onchange="TorrentsMod.selectAll(this.checked)"></th>
{{end}}
<th class="tr-cat">{{call $.T "category"}}</th>
<th class="tr-lang"></th>
<th class="tr-name">
<a href="{{ genSearchWithOrdering .URL "1" }}">{{call $.T "name"}}<span class="sort-arrows">{{ genSortArrows .URL "1" }}</span></a>
</th>
@ -41,7 +46,7 @@
<input data-name="{{ .Name }}" type="checkbox" id="torrent_cb_{{ .ID }}" name="torrent_id" value="{{ .ID }}">
</td>
{{ end }}
<td class="tr-cat home-td">
<td class="tr-cat home-td" {{if eq (FlagCode .Language) ""}}colspan="2"{{end}}>
<a href="{{$.URL.Parse (printf "/search?c=%s_%s" .Category .SubCategory) }}">
{{ if Sukebei }}
<img alt="{{ call $.T (CategoryName .Category .SubCategory) }}" src="{{$.URL.Parse (printf "/img/torrents/sukebei/%s%s.png" .Category .SubCategory) }}" title="{{ call $.T (CategoryName .Category .SubCategory) }}">
@ -50,6 +55,11 @@
{{ end}}
</a>
</td>
{{if ne (FlagCode .Language) ""}}
<td class="tr-lang tr-flag home-td">
<img src="img/blank.gif" alt="{{ LanguageName .Language $.T }}" class="flag flag-{{FlagCode .Language}}" title="{{ LanguageName .Language $.T }}">
</td>
{{end}}
<td class="tr-name home-td">
<a href="{{genRoute "view_torrent" "id" ( print .ID ) }}">
{{.Name}}
@ -223,7 +233,7 @@
"<input data-name=\""+Templates.EncodeEntities(torrent.name)+"\" type=\"checkbox\" id=\"torrent_cb_"+torrent.id+"\" name=\"torrent_id\" value=\""+torrent.id+"\">"+
"</td>"+
{{ end }}
"<td class=\"tr-cat home-td\">"+
"<td class=\"tr-cat home-td\""+(torrent.language == "" ? " colspan=2" : "")+">"+
"<a href=\"{{$.URL.Parse "/search?c=" }}"+ torrent.category + "_" + torrent.sub_category +"\">"+
{{ if Sukebei }}
"<img src=\"{{ $.URL.Parse "/img/torrents/sukebei/" }}"+ torrent.category + torrent.sub_category+".png\" title=\""+ torrent.CategoryName +"\">"+
@ -232,6 +242,9 @@
{{ end }}
"</a>"+
"</td>"+
(torrent.language != "" ? "<td class=\"tr-lang tr-flag home-td\">" +
"<img src=\"img/blank.gif\" class=\"flag flag-"+flagCode(torrent.language)+"\">"+
"</td>" : "")+
"<td class=\"tr-name home-td\"><a href=\"/view/"+torrent.id+"\">"+Templates.EncodeEntities(torrent.name) +"</a></td>"+
"<td class=\"tr-cs home-td hide-xs\">"+
((torrent.comments.length > 0) ? "<i class=\"comment-icon\" title=\"{{ call $.T "comments" }}\">" + torrent.comments.length + "</i>" : "")+

Voir le fichier

@ -26,6 +26,18 @@
<option value="{{ $id_cat }}" {{if eq $.Form.Category $id_cat }}selected{{end}}>{{call $.T $name_cat }}</option>
{{ end }}
</select>
<h3>{{ call $.T "torrent_language" }}</h3>
<select name="language" id="language" class="form-input up-input" required>
<option value="">{{call $.T "select_a_torrent_language"}}</option>
<option value="other" {{if eq $.Form.Language "other"}}selected{{end}}>{{call $.T "language_other_name"}}</option>
<option value="multiple" {{if eq $.Form.Language "multiple"}}selected{{end}}>{{call $.T "language_multiple_name"}}</option>
{{ range $_, $language := (GetTorrentLanguages) }}
<option value="{{ $language }}" {{if eq $.Form.Language $language}}selected{{end}}>
{{LanguageName $language $.T}}
</option>
{{ end }}
</select>
<p>
<input type="checkbox" name="remake" id="remake" >
<label for="remake">{{call $.T "mark_as_remake"}}</label>

Voir le fichier

@ -24,6 +24,19 @@
{{ end }}
</select>
</div>
<div class="form-group">
<label for="language">{{ call $.T "torrent_language" }}</label>
<select name="language" id="language" class="form-input up-input" required>
<option value="">{{call $.T "select_a_torrent_language"}}</option>
<option value="other" {{if eq $.Form.Language "other"}}selected{{end}}>{{call $.T "language_other_name"}}</option>
<option value="multiple" {{if eq $.Form.Language "multiple"}}selected{{end}}>{{call $.T "language_multiple_name"}}</option>
{{ range $_, $language := (GetTorrentLanguages) }}
<option value="{{ $language }}" {{if eq $.Form.Language $language}}selected{{end}}>
{{LanguageName $language $.T}}
</option>
{{ end }}
</select>
</div>
<div class="form-group">
<input type="checkbox" name="remake" id="remake" {{ if .Remake }}checked{{end}}>
<label for="remake">{{call $.T "mark_as_remake"}}</label>

Voir le fichier

@ -1,5 +1,9 @@
{{define "title"}}{{.Torrent.Name}}{{end}}
{{define "contclass"}}cont-view {{if eq .Torrent.Status 2}}remake{{end}} {{if eq .Torrent.Status 3}}trusted{{end}} {{if eq .Torrent.Status 4}}aplus{{end}}{{end}}
{{define "additional_header"}}
<link rel="stylesheet" href="/css/flags/flags.min.css">
<link rel="stylesheet" href="/css/flags/custom_flags.css">
{{end}}
{{ define "make_treeview" }}
{{ range $index, $folder := .Folder.Folders }}
{{ $folderId := (print $.IdentifierChain "_" $index) }}
@ -61,6 +65,11 @@
<td class="torrent-info-td torrent-info-label">{{call $.T "size"}}:</td><td class="torrent-view-td torrent-info-data">{{ fileSize .Filesize $.T }}</td>
<td class="torrent-info-td torrent-info-label">{{call $.T "last_scraped"}}</td><td class="torrent-info-td{{if not .LastScrape.IsZero}} date-full">{{formatDateRFC .LastScrape}}{{else}}">{{call $.T "unknown"}}{{end}}</td>
</tr>
{{if ne (FlagCode .Language) ""}}
<tr class="torrent-info-row">
<td class="torrent-info-td torrent-info-label">{{call $.T "torrent_language"}}:</td><td class="tr-flag torrent-view-td torrent-info-data"><img src="/img/blank.gif" alt="{{ LanguageName .Language $.T }}" class="flag flag-{{FlagCode .Language}}" title="{{ LanguageName .Language $.T }}"></img> {{ LanguageName .Language $.T }}</td>
</tr>
{{end}}
</table>
<div class="torrent-buttons">
<a href="{{.Magnet}}" class="form-input btn-green"><div class="magnet-icon"></div> {{call $.T "magnet_link"}}</a>

11
translations/README.md Fichier normal
Voir le fichier

@ -0,0 +1,11 @@
After creating a new translation, create a new translation string inside "en-us.all.json", like the following:
```
...
},
{
"id": "language_(languageCode)_name",
"translation": "(your language name, in English)"
},
...
```
where languageCode is the newly created ISO code (eg. ja-jp, pt-br).

Voir le fichier

@ -1222,5 +1222,109 @@
{
"id": "username_illegal",
"translation": "Username contains illegal characters!"
},
{
"id": "torrent_language",
"translation": "Torrent language"
},
{
"id": "select_a_torrent_language",
"translation": "Select a Torrent Language"
},
{
"id": "flag",
"translation": "gb"
},
{
"id": "language_en-us_name",
"translation": "English"
},
{
"id": "language_ca-es_name",
"translation": "Catalan"
},
{
"id": "language_de-de_name",
"translation": "German"
},
{
"id": "language_es-es_name",
"translation": "Spanish"
},
{
"id": "language_es-mx_name",
"translation": "Spanish (LATAM)"
},
{
"id": "language_fr-fr_name",
"translation": "French"
},
{
"id": "language_hu-hu_name",
"translation": "Hungarian"
},
{
"id": "language_is-is_name",
"translation": "Icelandic"
},
{
"id": "language_it-it_name",
"translation": "Italian"
},
{
"id": "language_ja-jp_name",
"translation": "Japanese"
},
{
"id": "language_ko-kr_name",
"translation": "Korean"
},
{
"id": "language_nb-no_name",
"translation": "Norwegian"
},
{
"id": "language_nl-nl_name",
"translation": "Dutch"
},
{
"id": "language_pt-br_name",
"translation": "Portuguese (Brazil)"
},
{
"id": "language_pt-pt_name",
"translation": "Portuguese (Portugal)"
},
{
"id": "language_ro-ro_name",
"translation": "Romanian"
},
{
"id": "language_ru-ru_name",
"translation": "Russian"
},
{
"id": "language_sv-se_name",
"translation": "Swedish"
},
{
"id": "language_th-th_name",
"translation": "Thai"
},
{
"id": "language_zh-cn_name",
"translation": "Chinese"
},
{
"id": "language_zh-tw_name",
"translation": "Chinese (Taiwan)"
},
{
"id": "language_other_name",
"translation": "Other"
},
{
"id": "language_multiple_name",
"translation": "Multiple Languages"
}
]

Voir le fichier

@ -94,6 +94,7 @@ func searchByQuery(r *http.Request, pagenum int, countAll bool, withUser bool, d
UserID: uint(torrentParam.UserID),
Max: uint(torrentParam.Max),
NotNull: torrentParam.NotNull,
Language: torrentParam.Language,
Query: torrentParam.NameLike,
}
// Convert back to non-json torrents
@ -117,6 +118,7 @@ func searchByQueryPostgres(r *http.Request, pagenum int, countAll bool, withUser
search.Page = pagenum
search.Query = r.URL.Query().Get("q")
search.Language = r.URL.Query().Get("lang")
userID, _ := strconv.Atoi(r.URL.Query().Get("userID"))
search.UserID = uint(userID)
fromID, _ := strconv.Atoi(r.URL.Query().Get("fromID"))
@ -255,6 +257,10 @@ func searchByQueryPostgres(r *http.Request, pagenum int, countAll bool, withUser
if len(search.NotNull) > 0 {
conditions = append(conditions, search.NotNull)
}
if search.Language != "" {
conditions = append(conditions, "language "+searchOperator)
parameters.Params = append(parameters.Params, "%"+search.Language+"%")
}
searchQuerySplit := strings.Fields(search.Query)
for _, word := range searchQuerySplit {

Voir le fichier

@ -0,0 +1,53 @@
package torrentLanguages
import (
"strings"
"github.com/NyaaPantsu/nyaa/config"
"github.com/NyaaPantsu/nyaa/util/publicSettings"
)
var torrentLanguages []string
func initTorrentLanguages() {
languages := publicSettings.GetAvailableLanguages()
for code := range languages {
torrentLanguages = append(torrentLanguages, code)
}
// Also support languages we don't have a translation
for _, code := range config.Conf.Torrents.AdditionalLanguages {
torrentLanguages = append(torrentLanguages, code)
}
}
// GetTorrentLanguages returns a list of available torrent languages.
func GetTorrentLanguages() []string {
if torrentLanguages == nil {
initTorrentLanguages()
}
return torrentLanguages
}
// LanguageExists check if said language is available for torrents
func LanguageExists(lang string) bool {
langs := GetTorrentLanguages()
for _, code := range langs {
if code == lang {
return true
}
}
return false
}
// FlagFromLanguage reads the language's country code.
func FlagFromLanguage(lang string) string {
languageSplit := strings.Split(lang, "-")
if len(languageSplit) > 1 {
return languageSplit[1]
}
return ""
}

Voir le fichier

@ -0,0 +1,36 @@
package torrentLanguages
import (
"io/ioutil"
"path"
"strings"
"testing"
"github.com/NyaaPantsu/nyaa/config"
)
// run before config/parse.go:init()
var _ = func() (_ struct{}) {
config.ConfigPath = path.Join("..", "..", config.ConfigPath)
config.DefaultConfigPath = path.Join("..", "..", config.DefaultConfigPath)
config.Parse()
return
}()
func TestCSSFlags(t *testing.T) {
languages := GetTorrentLanguages()
flagsCSSPath := path.Join("..", "..", "public", "css", "flags", "flags.css")
file, err := ioutil.ReadFile(flagsCSSPath)
if err != nil {
t.Errorf("Failed to load flags.css: %v", err)
return
}
contents := string(file)
for _, language := range languages {
flag := FlagFromLanguage(language)
if !strings.Contains(contents, ".flag-"+flag) {
t.Errorf("flags.css does not contains class .flag-%s. You probably need to update it.", flag)
}
}
}