From e038da433f219c496bb14a7393fe4ecebdd2eff4 Mon Sep 17 00:00:00 2001 From: UncleRoger33 <66418211+UncleRoger33@users.noreply.github.com> Date: Thu, 30 Dec 2021 02:46:49 +0300 Subject: [PATCH 01/80] Fix broken and outdated links in the Help document (#2180) * Update Contributing.md Fixed readme.md link. * Update Introduction.md "/settings?tab=configuration" is no longer valid. It is "/settings?tab=libraries" now. * Update Interface.md Fixed broken "Stash Plex Theme" link. * Update ScraperDevelopment.md Fixed broken "golang fields" link. * Update Interactive.md Added missing title. * Update Contributing.md Changed link to the correct document --> https://github.com/stashapp/stash/pull/2180/files/4c71a7dc1ba09cba5c6d527b8b500c198c08772d#r776229444 --- ui/v2.5/src/docs/en/Contributing.md | 4 ++-- ui/v2.5/src/docs/en/Interactive.md | 2 ++ ui/v2.5/src/docs/en/Interface.md | 4 ++-- ui/v2.5/src/docs/en/Introduction.md | 4 ++-- ui/v2.5/src/docs/en/ScraperDevelopment.md | 4 ++-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ui/v2.5/src/docs/en/Contributing.md b/ui/v2.5/src/docs/en/Contributing.md index c59092105..885253a0c 100644 --- a/ui/v2.5/src/docs/en/Contributing.md +++ b/ui/v2.5/src/docs/en/Contributing.md @@ -6,7 +6,7 @@ Financial contributions are welcomed and are accepted using [Open Collective](ht ## Development-related -The Stash backend is written in golang with a sqlite database. The UI is written in react. Bug fixes, improvements and new features are welcomed. Please see the [README.md](https://github.com/stashapp/stash/raw/develop/README.md) file for details on how to get started. Assistance can be provided via our [Discord](https://discord.gg/2TsNFKt). +The Stash backend is written in golang with a sqlite database. The UI is written in react. Bug fixes, improvements and new features are welcomed. Please see the [README.md](https://github.com/stashapp/stash/blob/develop/docs/DEVELOPMENT.md) file for details on how to get started. Assistance can be provided via our [Discord](https://discord.gg/2TsNFKt). ## Documentation @@ -44,4 +44,4 @@ We welcome contributions for future improvements and features, and bug reports h ## Providing support -Offering support for new users on [Discord](https://discord.gg/2TsNFKt) is also welcomed. \ No newline at end of file +Offering support for new users on [Discord](https://discord.gg/2TsNFKt) is also welcomed. diff --git a/ui/v2.5/src/docs/en/Interactive.md b/ui/v2.5/src/docs/en/Interactive.md index 53c4bd45e..831109aab 100644 --- a/ui/v2.5/src/docs/en/Interactive.md +++ b/ui/v2.5/src/docs/en/Interactive.md @@ -1,3 +1,5 @@ +# Interactivity + Stash currently supports syncing with Handy devices, using funscript files. In order for stash to connect to your Handy device, the Handy Connection Key must be entered in Settings -> Interface. diff --git a/ui/v2.5/src/docs/en/Interface.md b/ui/v2.5/src/docs/en/Interface.md index 9307cd6a1..7bfe1722e 100644 --- a/ui/v2.5/src/docs/en/Interface.md +++ b/ui/v2.5/src/docs/en/Interface.md @@ -24,7 +24,7 @@ The maximum loop duration option allows looping of shorter videos. Set this valu The stash UI can be customised using custom CSS. See [here](https://github.com/stashapp/stash/wiki/Custom-CSS-snippets) for a community-curated set of CSS snippets to customise your UI. -[Stash Plex Theme](https://github.com/stashapp/stash/wiki/Stash-Plex-Theme) is a community created theme inspired by the popular Plex interface. +[Stash Plex Theme](https://github.com/stashapp/stash/wiki/Theme-Plex) is a community created theme inspired by the popular Plex interface. ## Custom served folders @@ -50,4 +50,4 @@ custom_served_folders: The `background.png` and `noise.png` files can be placed in the `custom` folder, then in the custom css, the `./background.png` and `./noise.png` strings can be replaced with `/custom/background.png` and `/custom/noise.png` respectively. -Other applications are to add custom UIs to stash, accessible via `/custom`. \ No newline at end of file +Other applications are to add custom UIs to stash, accessible via `/custom`. diff --git a/ui/v2.5/src/docs/en/Introduction.md b/ui/v2.5/src/docs/en/Introduction.md index e40f6c880..92c07a834 100644 --- a/ui/v2.5/src/docs/en/Introduction.md +++ b/ui/v2.5/src/docs/en/Introduction.md @@ -1,7 +1,7 @@ # Introduction -Stash works by cataloging your media using the paths that you provide. Once you have [configured](/settings?tab=configuration) the locations where your media is stored, you can click the Scan button in [`Settings -> Tasks`](/settings?tab=tasks) and stash will begin scanning and importing your media into its library. +Stash works by cataloging your media using the paths that you provide. Once you have [configured](/settings?tab=library) the locations where your media is stored, you can click the Scan button in [`Settings -> Tasks`](/settings?tab=tasks) and stash will begin scanning and importing your media into its library. For the best experience, it is recommmended that after a scan is finished, that video previews and sprites are generated. You can do this in [`Settings -> Tasks`](/settings?tab=tasks). Note that currently it is only possible to perform one task at a time and there is no task queue, so the Generate task should be performed after Scan is complete. -Once your media is imported, you are ready to begin creating Performers, Studios and Tags, and curating your content! \ No newline at end of file +Once your media is imported, you are ready to begin creating Performers, Studios and Tags, and curating your content! diff --git a/ui/v2.5/src/docs/en/ScraperDevelopment.md b/ui/v2.5/src/docs/en/ScraperDevelopment.md index a382fe2a0..f1f802d86 100644 --- a/ui/v2.5/src/docs/en/ScraperDevelopment.md +++ b/ui/v2.5/src/docs/en/ScraperDevelopment.md @@ -273,7 +273,7 @@ Collectively, these configurations are known as mapped scraping configurations. A mapped scraping configuration may contain a `common` field, and must contain `performer`, `scene`, `movie` or `gallery` depending on the scraping type it is configured for. -Within the `performer`/`scene`/`movie`/`gallery` field are key/value pairs corresponding to the [golang fields](#object-fields) on the performer/scene object. These fields are case-sensitive. +Within the `performer`/`scene`/`movie`/`gallery` field are key/value pairs corresponding to the [golang fields](/help/ScraperDevelopment.md#object-fields) on the performer/scene object. These fields are case-sensitive. The values of these may be either a simple selector value, which tells the system where to get the value of the field from, or a more advanced configuration (see below). For example, for an xpath configuration: @@ -822,4 +822,4 @@ Rating Studio (see Studio Fields) Tags (see Tag fields) Performers (list of Performer fields) -``` \ No newline at end of file +``` From 7f831524f6d863dfe8441f673f3c16148e5c7f6e Mon Sep 17 00:00:00 2001 From: peolic <66393006+peolic@users.noreply.github.com> Date: Thu, 30 Dec 2021 01:49:47 +0200 Subject: [PATCH 02/80] Explain recursive common fragments edge case (#2183) --- ui/v2.5/src/docs/en/ScraperDevelopment.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ui/v2.5/src/docs/en/ScraperDevelopment.md b/ui/v2.5/src/docs/en/ScraperDevelopment.md index f1f802d86..29ee29ecd 100644 --- a/ui/v2.5/src/docs/en/ScraperDevelopment.md +++ b/ui/v2.5/src/docs/en/ScraperDevelopment.md @@ -319,11 +319,25 @@ The `common` field is used to configure selector fragments that can be reference common: $infoPiece: //div[@class="infoPiece"]/span performer: - Measurements: $infoPiece[text() = 'Measurements:']/../span[@class="smallInfo"] + Measurements: $infoPiece[text() = 'Measurements:']/../span[@class="smallInfo"] ``` The `Measurements` xpath string will replace `$infoPiece` with `//div[@class="infoPiece"]/span`, resulting in: `//div[@class="infoPiece"]/span[text() = 'Measurements:']/../span[@class="smallInfo"]`. +> **⚠️ Note:** Recursive common fragments are **not** supported. +Referencing a common fragment within another common fragment will cause an error. For example: +```yaml +common: + $info: //div[@class="info"] + # Referencing $info in $models will cause an error + $models: $info/a[@class="model"] +scene: + Title: $info/h1 + Performers: + Name: $models + URL: $models/@href +``` + ### Post-processing options Post-processing operations are contained in the `postProcess` key. Post-processing operations are performed in the order they are specified. The following post-processing operations are available: From 19fbf125c4c2b8c2a2d5e9787240f4315c293c81 Mon Sep 17 00:00:00 2001 From: peolic <66393006+peolic@users.noreply.github.com> Date: Tue, 4 Jan 2022 01:06:12 +0200 Subject: [PATCH 03/80] Fix CreatedAt/UpdatedAt timezone and missing time [+ v0.13.0 changelog] (#2190) * Fix CreatedAt/UpdatedAt timezone and missing time * Create v1.13.0 changelog --- ui/v2.5/src/components/Changelog/Changelog.tsx | 12 +++++++++--- ui/v2.5/src/components/Changelog/versions/v0130.md | 2 ++ .../Galleries/GalleryDetails/GalleryDetailPanel.tsx | 4 ++-- .../Images/ImageDetails/ImageDetailPanel.tsx | 4 ++-- .../Scenes/SceneDetails/SceneDetailPanel.tsx | 4 ++-- ui/v2.5/src/utils/text.ts | 13 +++++++++++-- 6 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 ui/v2.5/src/components/Changelog/versions/v0130.md diff --git a/ui/v2.5/src/components/Changelog/Changelog.tsx b/ui/v2.5/src/components/Changelog/Changelog.tsx index e237f94d6..ffd212a0c 100644 --- a/ui/v2.5/src/components/Changelog/Changelog.tsx +++ b/ui/v2.5/src/components/Changelog/Changelog.tsx @@ -15,6 +15,7 @@ import V090 from "./versions/v090.md"; import V0100 from "./versions/v0100.md"; import V0110 from "./versions/v0110.md"; import V0120 from "./versions/v0120.md"; +import V0130 from "./versions/v0130.md"; import { MarkdownPage } from "../Shared/MarkdownPage"; // to avoid use of explicit any @@ -53,9 +54,9 @@ const Changelog: React.FC = () => { // after new release: // add entry to releases, using the current* fields // then update the current fields. - const currentVersion = stashVersion || "v0.12.0"; + const currentVersion = stashVersion || "v0.13.0"; const currentDate = buildDate; - const currentPage = V0120; + const currentPage = V0130; const releases: IStashRelease[] = [ { @@ -64,9 +65,14 @@ const Changelog: React.FC = () => { page: currentPage, defaultOpen: true, }, + { + version: "v0.12.0", + date: "2021-12-29", + page: V0120, + }, { version: "v0.11.0", - date: "2021-11-15", + date: "2021-11-16", page: V0110, }, { diff --git a/ui/v2.5/src/components/Changelog/versions/v0130.md b/ui/v2.5/src/components/Changelog/versions/v0130.md new file mode 100644 index 000000000..d97e09b57 --- /dev/null +++ b/ui/v2.5/src/components/Changelog/versions/v0130.md @@ -0,0 +1,2 @@ +### 🐛 Bug fixes +* Fix timezone issue with Created/Updated dates in scene/image/gallery details pages. ([#2190](https://github.com/stashapp/stash/pull/2190)) diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx index 4c69a8bd0..dbd7908b4 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx @@ -90,11 +90,11 @@ export const GalleryDetailPanel: React.FC = ({ )}
:{" "} - {TextUtils.formatDate(intl, gallery.created_at)}{" "} + {TextUtils.formatDateTime(intl, gallery.created_at)}{" "}
:{" "} - {TextUtils.formatDate(intl, gallery.updated_at)}{" "} + {TextUtils.formatDateTime(intl, gallery.updated_at)}{" "}
{gallery.studio && ( diff --git a/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx b/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx index 831dabfb7..e15fd5683 100644 --- a/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx +++ b/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx @@ -98,13 +98,13 @@ export const ImageDetailPanel: React.FC = (props) => {
{" "} :{" "} - {TextUtils.formatDate(intl, props.image.created_at)}{" "} + {TextUtils.formatDateTime(intl, props.image.created_at)}{" "}
} {
:{" "} - {TextUtils.formatDate(intl, props.image.updated_at)}{" "} + {TextUtils.formatDateTime(intl, props.image.updated_at)}{" "}
} diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneDetailPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneDetailPanel.tsx index 4e5b03f09..346da5f22 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneDetailPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneDetailPanel.tsx @@ -116,11 +116,11 @@ export const SceneDetailPanel: React.FC = (props) => { )}
:{" "} - {TextUtils.formatDate(intl, props.scene.created_at)}{" "} + {TextUtils.formatDateTime(intl, props.scene.created_at)}{" "}
:{" "} - {TextUtils.formatDate(intl, props.scene.updated_at)}{" "} + {TextUtils.formatDateTime(intl, props.scene.updated_at)}{" "}
{props.scene.studio && ( diff --git a/ui/v2.5/src/utils/text.ts b/ui/v2.5/src/utils/text.ts index e90de3cd1..7404ab9db 100644 --- a/ui/v2.5/src/utils/text.ts +++ b/ui/v2.5/src/utils/text.ts @@ -284,14 +284,22 @@ const sanitiseURL = (url?: string, siteURL?: URL) => { return `https://${url}`; }; -const formatDate = (intl: IntlShape, date?: string) => { +const formatDate = (intl: IntlShape, date?: string, utc = true) => { if (!date) { return ""; } - return intl.formatDate(date, { format: "long", timeZone: "utc" }); + return intl.formatDate(date, { + format: "long", + timeZone: utc ? "utc" : undefined, + }); }; +const formatDateTime = (intl: IntlShape, dateTime?: string, utc = false) => + `${formatDate(intl, dateTime, utc)} ${intl.formatTime(dateTime, { + timeZone: utc ? "utc" : undefined, + })}`; + const capitalize = (val: string) => val .replace(/^[-_]*(.)/, (_, c) => c.toUpperCase()) @@ -311,6 +319,7 @@ const TextUtils = { twitterURL, instagramURL, formatDate, + formatDateTime, capitalize, secondsAsTimeString, }; From 0c0bdd4e21441e670a79fe4de412a6defb7f141b Mon Sep 17 00:00:00 2001 From: bnkai <48220860+bnkai@users.noreply.github.com> Date: Tue, 4 Jan 2022 02:30:42 +0200 Subject: [PATCH 04/80] Fix oshash calculation for symlinks (#2198) --- pkg/file/scan.go | 16 +++++++++++++++- .../src/components/Changelog/versions/v0130.md | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pkg/file/scan.go b/pkg/file/scan.go index 4c0ac3152..672fee853 100644 --- a/pkg/file/scan.go +++ b/pkg/file/scan.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "io/fs" + "os" "strconv" "time" @@ -117,6 +118,19 @@ func (o Scanner) generateHashes(f *models.File, file SourceFile, regenerate bool if o.CalculateOSHash && (regenerate || f.OSHash == "") { logger.Infof("Calculating oshash for %s ...", f.Path) + size := file.FileInfo().Size() + + // #2196 for symlinks + // get the size of the actual file, not the symlink + if file.FileInfo().Mode()&os.ModeSymlink == os.ModeSymlink { + fi, err := os.Stat(f.Path) + if err != nil { + return false, err + } + logger.Debugf("File <%s> is symlink. Size changed from <%d> to <%d>", f.Path, size, fi.Size()) + size = fi.Size() + } + src, err = file.Open() if err != nil { return false, err @@ -130,7 +144,7 @@ func (o Scanner) generateHashes(f *models.File, file SourceFile, regenerate bool // regenerate hash var oshash string - oshash, err = o.Hasher.OSHash(seekSrc, file.FileInfo().Size()) + oshash, err = o.Hasher.OSHash(seekSrc, size) if err != nil { return false, fmt.Errorf("error generating oshash for %s: %w", file.Path(), err) } diff --git a/ui/v2.5/src/components/Changelog/versions/v0130.md b/ui/v2.5/src/components/Changelog/versions/v0130.md index d97e09b57..33d05489d 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0130.md +++ b/ui/v2.5/src/components/Changelog/versions/v0130.md @@ -1,2 +1,3 @@ ### 🐛 Bug fixes +* Fix error when scanning symlinks. ([#2196](https://github.com/stashapp/stash/issues/2196)) * Fix timezone issue with Created/Updated dates in scene/image/gallery details pages. ([#2190](https://github.com/stashapp/stash/pull/2190)) From a2bfa9ee79abb56b15b836d168116d2e81c2fe98 Mon Sep 17 00:00:00 2001 From: InfiniteTF Date: Tue, 4 Jan 2022 02:15:38 +0100 Subject: [PATCH 05/80] Fix stash-box batch performer birthdate update (#2189) --- pkg/manager/task_stash_box_tag.go | 2 +- ui/v2.5/src/components/Changelog/versions/v0130.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/manager/task_stash_box_tag.go b/pkg/manager/task_stash_box_tag.go index dbfa28073..99fdbd7a1 100644 --- a/pkg/manager/task_stash_box_tag.go +++ b/pkg/manager/task_stash_box_tag.go @@ -261,7 +261,7 @@ func getDate(val *string) models.SQLiteDate { if val == nil { return models.SQLiteDate{Valid: false} } else { - return models.SQLiteDate{String: *val, Valid: false} + return models.SQLiteDate{String: *val, Valid: true} } } diff --git a/ui/v2.5/src/components/Changelog/versions/v0130.md b/ui/v2.5/src/components/Changelog/versions/v0130.md index 33d05489d..7039315fb 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0130.md +++ b/ui/v2.5/src/components/Changelog/versions/v0130.md @@ -1,3 +1,4 @@ ### 🐛 Bug fixes +* Fix stash-box batch performer task not setting birthdate. ([#2189](https://github.com/stashapp/stash/pull/2189)) * Fix error when scanning symlinks. ([#2196](https://github.com/stashapp/stash/issues/2196)) * Fix timezone issue with Created/Updated dates in scene/image/gallery details pages. ([#2190](https://github.com/stashapp/stash/pull/2190)) From bd784cdf96d1fe8915ad11152a6a33a19c667c64 Mon Sep 17 00:00:00 2001 From: InfiniteTF Date: Tue, 4 Jan 2022 02:55:45 +0100 Subject: [PATCH 06/80] Fix conversion of multi word stash-box enums (#2191) --- pkg/scraper/stashbox/stash_box.go | 26 +++++++++++++++++-- .../components/Changelog/versions/v0130.md | 1 + 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/pkg/scraper/stashbox/stash_box.go b/pkg/scraper/stashbox/stash_box.go index 087bce242..d8c817b6a 100644 --- a/pkg/scraper/stashbox/stash_box.go +++ b/pkg/scraper/stashbox/stash_box.go @@ -468,7 +468,7 @@ func findURL(urls []*graphql.URLFragment, urlType string) *string { func enumToStringPtr(e fmt.Stringer, titleCase bool) *string { if e != nil { - ret := e.String() + ret := strings.ReplaceAll(e.String(), "_", " ") if titleCase { ret = strings.Title(strings.ToLower(ret)) } @@ -478,6 +478,28 @@ func enumToStringPtr(e fmt.Stringer, titleCase bool) *string { return nil } +func translateGender(gender *graphql.GenderEnum) *string { + var res models.GenderEnum + switch *gender { + case graphql.GenderEnumMale: + res = models.GenderEnumMale + case graphql.GenderEnumFemale: + res = models.GenderEnumFemale + case graphql.GenderEnumIntersex: + res = models.GenderEnumIntersex + case graphql.GenderEnumTransgenderFemale: + res = models.GenderEnumTransgenderFemale + case graphql.GenderEnumTransgenderMale: + res = models.GenderEnumTransgenderMale + } + + if res != "" { + strVal := res.String() + return &strVal + } + return nil +} + func formatMeasurements(m graphql.MeasurementsFragment) *string { if m.BandSize != nil && m.CupSize != nil && m.Hip != nil && m.Waist != nil { ret := fmt.Sprintf("%d%s-%d-%d", *m.BandSize, *m.CupSize, *m.Waist, *m.Hip) @@ -587,7 +609,7 @@ func performerFragmentToScrapedScenePerformer(p graphql.PerformerFragment) *mode } if p.Gender != nil { - sp.Gender = enumToStringPtr(p.Gender, false) + sp.Gender = translateGender(p.Gender) } if p.Ethnicity != nil { diff --git a/ui/v2.5/src/components/Changelog/versions/v0130.md b/ui/v2.5/src/components/Changelog/versions/v0130.md index 7039315fb..6064822be 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0130.md +++ b/ui/v2.5/src/components/Changelog/versions/v0130.md @@ -1,4 +1,5 @@ ### 🐛 Bug fixes +* Fix stash-box scraping including underscores in ethnicity. ([#2191](https://github.com/stashapp/stash/pull/2191)) * Fix stash-box batch performer task not setting birthdate. ([#2189](https://github.com/stashapp/stash/pull/2189)) * Fix error when scanning symlinks. ([#2196](https://github.com/stashapp/stash/issues/2196)) * Fix timezone issue with Created/Updated dates in scene/image/gallery details pages. ([#2190](https://github.com/stashapp/stash/pull/2190)) From 849c590b2a363b8012a6705a5bf0bf017efddea4 Mon Sep 17 00:00:00 2001 From: bnkai <48220860+bnkai@users.noreply.github.com> Date: Tue, 4 Jan 2022 04:46:53 +0200 Subject: [PATCH 07/80] Fix scrubber sprite creation for short video files (#2167) * Fix scrubber sprite creation for small files * accept only valid ffprobe nbReadFrames --- pkg/ffmpeg/encoder_sprite_screenshot.go | 29 ++++++ pkg/ffmpeg/ffprobe.go | 30 ++++++- pkg/ffmpeg/types.go | 1 + pkg/manager/generator_sprite.go | 88 +++++++++++++++---- .../components/Changelog/versions/v0130.md | 1 + 5 files changed, 131 insertions(+), 18 deletions(-) diff --git a/pkg/ffmpeg/encoder_sprite_screenshot.go b/pkg/ffmpeg/encoder_sprite_screenshot.go index cba560430..d0068cb2a 100644 --- a/pkg/ffmpeg/encoder_sprite_screenshot.go +++ b/pkg/ffmpeg/encoder_sprite_screenshot.go @@ -8,6 +8,7 @@ import ( type SpriteScreenshotOptions struct { Time float64 + Frame int Width int } @@ -36,3 +37,31 @@ func (e *Encoder) SpriteScreenshot(probeResult VideoFile, options SpriteScreensh return img, err } + +// SpriteScreenshotSlow uses the select filter to get a single frame from a videofile instead of seeking +// It is very slow and should only be used for files with very small duration in secs / frame count +func (e *Encoder) SpriteScreenshotSlow(probeResult VideoFile, options SpriteScreenshotOptions) (image.Image, error) { + args := []string{ + "-v", "error", + "-i", probeResult.Path, + "-vsync", "0", // do not create/drop frames + "-vframes", "1", + "-vf", fmt.Sprintf("select=eq(n\\,%d),scale=%v:-1", options.Frame, options.Width), // keep only frame number options.Frame + "-c:v", "bmp", + "-f", "rawvideo", + "-", + } + data, err := e.run(probeResult.Path, args, nil) + if err != nil { + return nil, err + } + + reader := strings.NewReader(data) + + img, _, err := image.Decode(reader) + if err != nil { + return nil, err + } + + return img, err +} diff --git a/pkg/ffmpeg/ffprobe.go b/pkg/ffmpeg/ffprobe.go index dfde16d8b..a24d2b39d 100644 --- a/pkg/ffmpeg/ffprobe.go +++ b/pkg/ffmpeg/ffprobe.go @@ -218,6 +218,7 @@ type VideoFile struct { Height int FrameRate float64 Rotation int64 + FrameCount int64 AudioCodec string } @@ -242,6 +243,24 @@ func (f *FFProbe) NewVideoFile(videoPath string, stripExt bool) (*VideoFile, err return parse(videoPath, probeJSON, stripExt) } +// GetReadFrameCount counts the actual frames of the video file +func (f *FFProbe) GetReadFrameCount(vf *VideoFile) (int64, error) { + args := []string{"-v", "quiet", "-print_format", "json", "-count_frames", "-show_format", "-show_streams", "-show_error", vf.Path} + out, err := exec.Command(string(*f), args...).Output() + + if err != nil { + return 0, fmt.Errorf("FFProbe encountered an error with <%s>.\nError JSON:\n%s\nError: %s", vf.Path, string(out), err.Error()) + } + + probeJSON := &FFProbeJSON{} + if err := json.Unmarshal(out, probeJSON); err != nil { + return 0, fmt.Errorf("error unmarshalling video data for <%s>: %s", vf.Path, err.Error()) + } + + fc, err := parse(vf.Path, probeJSON, false) + return fc.FrameCount, err +} + func parse(filePath string, probeJSON *FFProbeJSON, stripExt bool) (*VideoFile, error) { if probeJSON == nil { return nil, fmt.Errorf("failed to get ffprobe json for <%s>", filePath) @@ -263,8 +282,8 @@ func parse(filePath string, probeJSON *FFProbeJSON, stripExt bool) (*VideoFile, } result.Comment = probeJSON.Format.Tags.Comment - result.Bitrate, _ = strconv.ParseInt(probeJSON.Format.BitRate, 10, 64) + result.Container = probeJSON.Format.FormatName duration, _ := strconv.ParseFloat(probeJSON.Format.Duration, 64) result.Duration = math.Round(duration*100) / 100 @@ -288,6 +307,15 @@ func parse(filePath string, probeJSON *FFProbeJSON, stripExt bool) (*VideoFile, if videoStream != nil { result.VideoStream = videoStream result.VideoCodec = videoStream.CodecName + result.FrameCount, _ = strconv.ParseInt(videoStream.NbFrames, 10, 64) + if videoStream.NbReadFrames != "" { // if ffprobe counted the frames use that instead + fc, _ := strconv.ParseInt(videoStream.NbReadFrames, 10, 64) + if fc > 0 { + result.FrameCount, _ = strconv.ParseInt(videoStream.NbReadFrames, 10, 64) + } else { + logger.Debugf("[ffprobe] <%s> invalid Read Frames count", videoStream.NbReadFrames) + } + } result.VideoBitrate, _ = strconv.ParseInt(videoStream.BitRate, 10, 64) var framerate float64 if strings.Contains(videoStream.AvgFrameRate, "/") { diff --git a/pkg/ffmpeg/types.go b/pkg/ffmpeg/types.go index ed3fadf67..d239c6cdf 100644 --- a/pkg/ffmpeg/types.go +++ b/pkg/ffmpeg/types.go @@ -70,6 +70,7 @@ type FFProbeStream struct { Level int `json:"level,omitempty"` NalLengthSize string `json:"nal_length_size,omitempty"` NbFrames string `json:"nb_frames"` + NbReadFrames string `json:"nb_read_frames"` PixFmt string `json:"pix_fmt,omitempty"` Profile string `json:"profile"` RFrameRate string `json:"r_frame_rate"` diff --git a/pkg/manager/generator_sprite.go b/pkg/manager/generator_sprite.go index c374217ce..72c45e124 100644 --- a/pkg/manager/generator_sprite.go +++ b/pkg/manager/generator_sprite.go @@ -25,6 +25,7 @@ type SpriteGenerator struct { VTTOutputPath string Rows int Columns int + SlowSeek bool // use alternate seek function, very slow! Overwrite bool } @@ -34,17 +35,33 @@ func NewSpriteGenerator(videoFile ffmpeg.VideoFile, videoChecksum string, imageO if !exists { return nil, err } + slowSeek := false + chunkCount := rows * cols - // FFMPEG bombs out if we try to request 89 snapshots from a 2 second video - if videoFile.Duration < 3 { - return nil, errors.New("video too short to create sprite") + // For files with small duration / low frame count try to seek using frame number intead of seconds + if videoFile.Duration < 5 || (0 < videoFile.FrameCount && videoFile.FrameCount <= int64(chunkCount)) { // some files can have FrameCount == 0, only use SlowSeek if duration < 5 + if videoFile.Duration <= 0 { + s := fmt.Sprintf("video %s: duration(%.3f)/frame count(%d) invalid, skipping sprite creation", videoFile.Path, videoFile.Duration, videoFile.FrameCount) + return nil, errors.New(s) + } + logger.Warnf("[generator] video %s too short (%.3fs, %d frames), using frame seeking", videoFile.Path, videoFile.Duration, videoFile.FrameCount) + slowSeek = true + // do an actual frame count of the file ( number of frames = read frames) + ffprobe := GetInstance().FFProbe + fc, err := ffprobe.GetReadFrameCount(&videoFile) + if err == nil { + if fc != videoFile.FrameCount { + logger.Warnf("[generator] updating framecount (%d) for %s with read frames count (%d)", videoFile.FrameCount, videoFile.Path, fc) + videoFile.FrameCount = fc + } + } } generator, err := newGeneratorInfo(videoFile) if err != nil { return nil, err } - generator.ChunkCount = rows * cols + generator.ChunkCount = chunkCount if err := generator.configure(); err != nil { return nil, err } @@ -55,6 +72,7 @@ func NewSpriteGenerator(videoFile ffmpeg.VideoFile, videoChecksum string, imageO ImageOutputPath: imageOutputPath, VTTOutputPath: vttOutputPath, Rows: rows, + SlowSeek: slowSeek, Columns: cols, }, nil } @@ -75,23 +93,51 @@ func (g *SpriteGenerator) generateSpriteImage(encoder *ffmpeg.Encoder) error { if !g.Overwrite && g.imageExists() { return nil } - logger.Infof("[generator] generating sprite image for %s", g.Info.VideoFile.Path) - // Create `this.chunkCount` thumbnails in the tmp directory - stepSize := g.Info.VideoFile.Duration / float64(g.Info.ChunkCount) var images []image.Image - for i := 0; i < g.Info.ChunkCount; i++ { - time := float64(i) * stepSize - options := ffmpeg.SpriteScreenshotOptions{ - Time: time, - Width: 160, + if !g.SlowSeek { + logger.Infof("[generator] generating sprite image for %s", g.Info.VideoFile.Path) + // generate `ChunkCount` thumbnails + stepSize := g.Info.VideoFile.Duration / float64(g.Info.ChunkCount) + + for i := 0; i < g.Info.ChunkCount; i++ { + time := float64(i) * stepSize + + options := ffmpeg.SpriteScreenshotOptions{ + Time: time, + Width: 160, + } + + img, err := encoder.SpriteScreenshot(g.Info.VideoFile, options) + + if err != nil { + return err + } + images = append(images, img) } - img, err := encoder.SpriteScreenshot(g.Info.VideoFile, options) - if err != nil { - return err + } else { + logger.Infof("[generator] generating sprite image for %s (%d frames)", g.Info.VideoFile.Path, g.Info.VideoFile.FrameCount) + + stepFrame := float64(g.Info.VideoFile.FrameCount-1) / float64(g.Info.ChunkCount) + + for i := 0; i < g.Info.ChunkCount; i++ { + // generate exactly `ChunkCount` thumbnails, using duplicate frames if needed + frame := math.Round(float64(i) * stepFrame) + if frame >= math.MaxInt || frame <= math.MinInt { + return errors.New("invalid frame number conversion") + } + options := ffmpeg.SpriteScreenshotOptions{ + Frame: int(frame), + Width: 160, + } + img, err := encoder.SpriteScreenshotSlow(g.Info.VideoFile, options) + if err != nil { + return err + } + images = append(images, img) } - images = append(images, img) + } if len(images) == 0 { @@ -132,7 +178,15 @@ func (g *SpriteGenerator) generateSpriteVTT(encoder *ffmpeg.Encoder) error { width := image.Width / g.Columns height := image.Height / g.Rows - stepSize := float64(g.Info.NthFrame) / g.Info.FrameRate + var stepSize float64 + if !g.SlowSeek { + stepSize = float64(g.Info.NthFrame) / g.Info.FrameRate + } else { + // for files with a low framecount ( Date: Mon, 3 Jan 2022 19:04:50 -0800 Subject: [PATCH 08/80] Show counts on entity list tabs (#2169) --- .../components/Changelog/versions/v0130.md | 3 + .../Performers/PerformerDetails/Performer.tsx | 50 +++++++++++++++-- .../Settings/SettingsServicesPanel.tsx | 2 +- .../Studios/StudioDetails/Studio.tsx | 56 +++++++++++++++++-- .../src/components/Tags/TagDetails/Tag.tsx | 53 ++++++++++++++++-- ui/v2.5/src/index.scss | 4 ++ 6 files changed, 150 insertions(+), 18 deletions(-) diff --git a/ui/v2.5/src/components/Changelog/versions/v0130.md b/ui/v2.5/src/components/Changelog/versions/v0130.md index 87c95d5f7..90bb459b4 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0130.md +++ b/ui/v2.5/src/components/Changelog/versions/v0130.md @@ -1,3 +1,6 @@ +### 🎨 Improvements +Show counts on list tabs in Performer, Studio and Tag pages. ([#2169](https://github.com/stashapp/stash/pull/2169)) + ### 🐛 Bug fixes * Generate sprites for short video files. ([#2167](https://github.com/stashapp/stash/pull/2167)) * Fix stash-box scraping including underscores in ethnicity. ([#2191](https://github.com/stashapp/stash/pull/2191)) diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx index c31f7ef0a..94f69a1cd 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState } from "react"; -import { Button, Tabs, Tab } from "react-bootstrap"; +import { Button, Tabs, Tab, Badge } from "react-bootstrap"; import { FormattedMessage, useIntl } from "react-intl"; import { useParams, useHistory } from "react-router-dom"; import { Helmet } from "react-helmet"; @@ -147,16 +147,56 @@ const PerformerPage: React.FC = ({ performer }) => { - + + {intl.formatMessage({ id: "scenes" })} + + {intl.formatNumber(performer.scene_count ?? 0)} + + + } + > - + + {intl.formatMessage({ id: "galleries" })} + + {intl.formatNumber(performer.gallery_count ?? 0)} + + + } + > - + + {intl.formatMessage({ id: "images" })} + + {intl.formatNumber(performer.image_count ?? 0)} + + + } + > - + + {intl.formatMessage({ id: "movies" })} + + {intl.formatNumber(performer.movie_count ?? 0)} + + + } + > diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index 2bc7c9761..e15c29ead 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -117,7 +117,7 @@ export const SettingsServicesPanel: React.FC = () => { function renderDeadline(until?: string) { if (until) { const deadline = new Date(until); - return `until ${deadline.toLocaleString()}`; + return `until ${intl.formatDate(deadline)}`; } return ""; diff --git a/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx b/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx index 9d95ecea7..56875b40c 100644 --- a/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx +++ b/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx @@ -1,4 +1,4 @@ -import { Tabs, Tab } from "react-bootstrap"; +import { Tabs, Tab, Badge } from "react-bootstrap"; import React, { useEffect, useState } from "react"; import { useParams, useHistory } from "react-router-dom"; import { FormattedMessage, useIntl } from "react-intl"; @@ -216,16 +216,43 @@ const StudioPage: React.FC = ({ studio }) => { activeKey={activeTabKey} onSelect={setActiveTabKey} > - + + {intl.formatMessage({ id: "scenes" })} + + {intl.formatNumber(studio.scene_count ?? 0)} + + + } + > + {intl.formatMessage({ id: "galleries" })} + + {intl.formatNumber(studio.gallery_count ?? 0)} + + + } > - + + {intl.formatMessage({ id: "images" })} + + {intl.formatNumber(studio.image_count ?? 0)} + + + } + > = ({ studio }) => { > - + + {intl.formatMessage({ id: "movies" })} + + {intl.formatNumber(studio.movie_count ?? 0)} + + + } + > + {intl.formatMessage({ id: "subsidiary_studios" })} + + {intl.formatNumber(studio.child_studios?.length)} + + + } > diff --git a/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx b/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx index 99259be34..4b75d927f 100644 --- a/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx +++ b/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx @@ -1,4 +1,4 @@ -import { Tabs, Tab, Dropdown } from "react-bootstrap"; +import { Tabs, Tab, Dropdown, Badge } from "react-bootstrap"; import React, { useEffect, useState } from "react"; import { useParams, useHistory } from "react-router-dom"; import { FormattedMessage, useIntl } from "react-intl"; @@ -297,27 +297,68 @@ const TagPage: React.FC = ({ tag }) => { activeKey={activeTabKey} onSelect={setActiveTabKey} > - + + {intl.formatMessage({ id: "scenes" })} + + {intl.formatNumber(tag.scene_count ?? 0)} + + + } + > - + + {intl.formatMessage({ id: "images" })} + + {intl.formatNumber(tag.image_count ?? 0)} + + + } + > + {intl.formatMessage({ id: "galleries" })} + + {intl.formatNumber(tag.gallery_count ?? 0)} + + + } > + {intl.formatMessage({ id: "markers" })} + + {intl.formatNumber(tag.scene_marker_count ?? 0)} + + + } > + {intl.formatMessage({ id: "performers" })} + + {intl.formatNumber(tag.performer_count ?? 0)} + + + } > diff --git a/ui/v2.5/src/index.scss b/ui/v2.5/src/index.scss index 2ac93c8d3..a67e1e504 100755 --- a/ui/v2.5/src/index.scss +++ b/ui/v2.5/src/index.scss @@ -752,3 +752,7 @@ select { background: url("data:image/svg+xml;utf8,") no-repeat right 2px center; } + +.left-spacing { + margin-left: 0.5em; +} From 34aea876e89b1681bb0c44036a87b31b49d45ac5 Mon Sep 17 00:00:00 2001 From: InfiniteTF Date: Tue, 4 Jan 2022 04:20:31 +0100 Subject: [PATCH 09/80] Add stash-box credentials validation (#2173) --- .../documents/queries/settings/config.graphql | 7 + graphql/schema/schema.graphql | 1 + graphql/schema/types/config.graphql | 5 + graphql/stash-box/query.graphql | 6 + pkg/api/resolver_query_configuration.go | 38 +++ .../stashbox/graphql/generated_client.go | 303 ++++++++++-------- pkg/scraper/stashbox/stash_box.go | 4 + .../components/Changelog/versions/v0130.md | 3 +- .../Settings/StashBoxConfiguration.tsx | 43 ++- 9 files changed, 268 insertions(+), 142 deletions(-) diff --git a/graphql/documents/queries/settings/config.graphql b/graphql/documents/queries/settings/config.graphql index 4ee9d4ec6..0a4b076d2 100644 --- a/graphql/documents/queries/settings/config.graphql +++ b/graphql/documents/queries/settings/config.graphql @@ -11,3 +11,10 @@ query Directory($path: String) { directories } } + +query ValidateStashBox($input: StashBoxInput!) { + validateStashBoxCredentials(input: $input) { + valid + status + } +} diff --git a/graphql/schema/schema.graphql b/graphql/schema/schema.graphql index 3f6419fed..044dbf49a 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -136,6 +136,7 @@ type Query { "Desired collation locale. Determines the order of the directory result. eg. 'en-US', 'pt-BR', ..." locale: String = "en" ): Directory! + validateStashBoxCredentials(input: StashBoxInput!): StashBoxValidationResult! # System status systemStatus: SystemStatus! diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index 0bab8fd93..d4fe5aa31 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -391,3 +391,8 @@ type StashConfig { input GenerateAPIKeyInput { clear: Boolean } + +type StashBoxValidationResult { + valid: Boolean! + status: String! +} diff --git a/graphql/stash-box/query.graphql b/graphql/stash-box/query.graphql index 9bc24f70a..12db15ca5 100644 --- a/graphql/stash-box/query.graphql +++ b/graphql/stash-box/query.graphql @@ -156,3 +156,9 @@ query FindSceneByID($id: ID!) { mutation SubmitFingerprint($input: FingerprintSubmission!) { submitFingerprint(input: $input) } + +query Me { + me { + name + } +} diff --git a/pkg/api/resolver_query_configuration.go b/pkg/api/resolver_query_configuration.go index b54019b4c..771c32357 100644 --- a/pkg/api/resolver_query_configuration.go +++ b/pkg/api/resolver_query_configuration.go @@ -2,9 +2,12 @@ package api import ( "context" + "fmt" + "strings" "github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/scraper/stashbox" "github.com/stashapp/stash/pkg/utils" "golang.org/x/text/collate" ) @@ -188,3 +191,38 @@ func makeConfigDefaultsResult() *models.ConfigDefaultSettingsResult { DeleteGenerated: &deleteGeneratedDefault, } } + +func (r *queryResolver) ValidateStashBoxCredentials(ctx context.Context, input models.StashBoxInput) (*models.StashBoxValidationResult, error) { + client := stashbox.NewClient(models.StashBox{Endpoint: input.Endpoint, APIKey: input.APIKey}, r.txnManager) + user, err := client.GetUser(ctx) + + valid := user != nil && user.Me != nil + var status string + if valid { + status = fmt.Sprintf("Successfully authenticated as %s", user.Me.Name) + } else { + switch { + case strings.Contains(strings.ToLower(err.Error()), "doctype"): + // Index file returned rather than graphql + status = "Invalid endpoint" + case strings.Contains(err.Error(), "request failed"): + status = "No response from server" + case strings.HasPrefix(err.Error(), "invalid character") || + strings.HasPrefix(err.Error(), "illegal base64 data") || + err.Error() == "unexpected end of JSON input" || + err.Error() == "token contains an invalid number of segments": + status = "Malformed API key." + case err.Error() == "" || err.Error() == "signature is invalid": + status = "Invalid or expired API key." + default: + status = fmt.Sprintf("Unknown error: %s", err) + } + } + + result := models.StashBoxValidationResult{ + Valid: valid, + Status: status, + } + + return &result, nil +} diff --git a/pkg/scraper/stashbox/graphql/generated_client.go b/pkg/scraper/stashbox/graphql/generated_client.go index 8e0b31429..fd48d6528 100644 --- a/pkg/scraper/stashbox/graphql/generated_client.go +++ b/pkg/scraper/stashbox/graphql/generated_client.go @@ -180,45 +180,32 @@ type FindSceneByID struct { type SubmitFingerprintPayload struct { SubmitFingerprint bool "json:\"submitFingerprint\" graphql:\"submitFingerprint\"" } +type Me struct { + Me *struct { + Name string "json:\"name\" graphql:\"name\"" + } "json:\"me\" graphql:\"me\"" +} const FindSceneByFingerprintQuery = `query FindSceneByFingerprint ($fingerprint: FingerprintQueryInput!) { findSceneByFingerprint(fingerprint: $fingerprint) { ... SceneFragment } } -fragment FuzzyDateFragment on FuzzyDate { - date - accuracy -} -fragment SceneFragment on Scene { - id - title - details +fragment FingerprintFragment on Fingerprint { + algorithm + hash duration - date - urls { - ... URLFragment - } - images { - ... ImageFragment - } - studio { - ... StudioFragment - } - tags { - ... TagFragment - } - performers { - ... PerformerAppearanceFragment - } - fingerprints { - ... FingerprintFragment - } } fragment URLFragment on URL { url type } +fragment ImageFragment on Image { + id + url + width + height +} fragment StudioFragment on Studio { name id @@ -269,31 +256,49 @@ fragment PerformerFragment on Performer { ... BodyModificationFragment } } -fragment ImageFragment on Image { +fragment BodyModificationFragment on BodyModification { + location + description +} +fragment SceneFragment on Scene { id - url - width - height + title + details + duration + date + urls { + ... URLFragment + } + images { + ... ImageFragment + } + studio { + ... StudioFragment + } + tags { + ... TagFragment + } + performers { + ... PerformerAppearanceFragment + } + fingerprints { + ... FingerprintFragment + } } fragment TagFragment on Tag { name id } +fragment FuzzyDateFragment on FuzzyDate { + date + accuracy +} fragment MeasurementsFragment on Measurements { band_size cup_size waist hip } -fragment BodyModificationFragment on BodyModification { - location - description -} -fragment FingerprintFragment on Fingerprint { - algorithm - hash - duration -} ` func (c *Client) FindSceneByFingerprint(ctx context.Context, fingerprint FingerprintQueryInput, httpRequestOptions ...client.HTTPRequestOption) (*FindSceneByFingerprint, error) { @@ -314,12 +319,6 @@ const FindScenesByFullFingerprintsQuery = `query FindScenesByFullFingerprints ($ ... SceneFragment } } -fragment ImageFragment on Image { - id - url - width - height -} fragment StudioFragment on Studio { name id @@ -330,10 +329,6 @@ fragment StudioFragment on Studio { ... ImageFragment } } -fragment TagFragment on Tag { - name - id -} fragment PerformerFragment on Performer { id name @@ -372,11 +367,14 @@ fragment FuzzyDateFragment on FuzzyDate { date accuracy } -fragment MeasurementsFragment on Measurements { - band_size - cup_size - waist - hip +fragment BodyModificationFragment on BodyModification { + location + description +} +fragment FingerprintFragment on Fingerprint { + algorithm + hash + duration } fragment SceneFragment on Scene { id @@ -403,24 +401,31 @@ fragment SceneFragment on Scene { ... FingerprintFragment } } +fragment URLFragment on URL { + url + type +} +fragment ImageFragment on Image { + id + url + width + height +} +fragment TagFragment on Tag { + name + id +} fragment PerformerAppearanceFragment on PerformerAppearance { as performer { ... PerformerFragment } } -fragment BodyModificationFragment on BodyModification { - location - description -} -fragment FingerprintFragment on Fingerprint { - algorithm - hash - duration -} -fragment URLFragment on URL { - url - type +fragment MeasurementsFragment on Measurements { + band_size + cup_size + waist + hip } ` @@ -452,18 +457,19 @@ fragment ImageFragment on Image { width height } -fragment TagFragment on Tag { - name - id -} fragment FuzzyDateFragment on FuzzyDate { date accuracy } -fragment FingerprintFragment on Fingerprint { - algorithm - hash - duration +fragment MeasurementsFragment on Measurements { + band_size + cup_size + waist + hip +} +fragment BodyModificationFragment on BodyModification { + location + description } fragment SceneFragment on Scene { id @@ -500,6 +506,10 @@ fragment StudioFragment on Studio { ... ImageFragment } } +fragment TagFragment on Tag { + name + id +} fragment PerformerAppearanceFragment on PerformerAppearance { as performer { @@ -540,15 +550,10 @@ fragment PerformerFragment on Performer { ... BodyModificationFragment } } -fragment MeasurementsFragment on Measurements { - band_size - cup_size - waist - hip -} -fragment BodyModificationFragment on BodyModification { - location - description +fragment FingerprintFragment on Fingerprint { + algorithm + hash + duration } ` @@ -570,30 +575,6 @@ const SearchPerformerQuery = `query SearchPerformer ($term: String!) { ... PerformerFragment } } -fragment URLFragment on URL { - url - type -} -fragment ImageFragment on Image { - id - url - width - height -} -fragment FuzzyDateFragment on FuzzyDate { - date - accuracy -} -fragment MeasurementsFragment on Measurements { - band_size - cup_size - waist - hip -} -fragment BodyModificationFragment on BodyModification { - location - description -} fragment PerformerFragment on Performer { id name @@ -628,6 +609,30 @@ fragment PerformerFragment on Performer { ... BodyModificationFragment } } +fragment URLFragment on URL { + url + type +} +fragment ImageFragment on Image { + id + url + width + height +} +fragment FuzzyDateFragment on FuzzyDate { + date + accuracy +} +fragment MeasurementsFragment on Measurements { + band_size + cup_size + waist + hip +} +fragment BodyModificationFragment on BodyModification { + location + description +} ` func (c *Client) SearchPerformer(ctx context.Context, term string, httpRequestOptions ...client.HTTPRequestOption) (*SearchPerformer, error) { @@ -732,6 +737,35 @@ fragment ImageFragment on Image { width height } +fragment TagFragment on Tag { + name + id +} +fragment PerformerAppearanceFragment on PerformerAppearance { + as + performer { + ... PerformerFragment + } +} +fragment FuzzyDateFragment on FuzzyDate { + date + accuracy +} +fragment MeasurementsFragment on Measurements { + band_size + cup_size + waist + hip +} +fragment FingerprintFragment on Fingerprint { + algorithm + hash + duration +} +fragment URLFragment on URL { + url + type +} fragment StudioFragment on Studio { name id @@ -742,10 +776,6 @@ fragment StudioFragment on Studio { ... ImageFragment } } -fragment TagFragment on Tag { - name - id -} fragment PerformerFragment on Performer { id name @@ -780,16 +810,9 @@ fragment PerformerFragment on Performer { ... BodyModificationFragment } } -fragment MeasurementsFragment on Measurements { - band_size - cup_size - waist - hip -} -fragment FingerprintFragment on Fingerprint { - algorithm - hash - duration +fragment BodyModificationFragment on BodyModification { + location + description } fragment SceneFragment on Scene { id @@ -816,24 +839,6 @@ fragment SceneFragment on Scene { ... FingerprintFragment } } -fragment URLFragment on URL { - url - type -} -fragment BodyModificationFragment on BodyModification { - location - description -} -fragment PerformerAppearanceFragment on PerformerAppearance { - as - performer { - ... PerformerFragment - } -} -fragment FuzzyDateFragment on FuzzyDate { - date - accuracy -} ` func (c *Client) FindSceneByID(ctx context.Context, id string, httpRequestOptions ...client.HTTPRequestOption) (*FindSceneByID, error) { @@ -866,3 +871,21 @@ func (c *Client) SubmitFingerprint(ctx context.Context, input FingerprintSubmiss return &res, nil } + +const MeQuery = `query Me { + me { + name + } +} +` + +func (c *Client) Me(ctx context.Context, httpRequestOptions ...client.HTTPRequestOption) (*Me, error) { + vars := map[string]interface{}{} + + var res Me + if err := c.Client.Post(ctx, MeQuery, &res, vars, httpRequestOptions...); err != nil { + return nil, err + } + + return &res, nil +} diff --git a/pkg/scraper/stashbox/stash_box.go b/pkg/scraper/stashbox/stash_box.go index d8c817b6a..2ffefa2ff 100644 --- a/pkg/scraper/stashbox/stash_box.go +++ b/pkg/scraper/stashbox/stash_box.go @@ -753,3 +753,7 @@ func (c Client) FindStashBoxPerformerByName(ctx context.Context, name string) (* return ret, nil } + +func (c Client) GetUser(ctx context.Context) (*graphql.Me, error) { + return c.client.Me(ctx) +} diff --git a/ui/v2.5/src/components/Changelog/versions/v0130.md b/ui/v2.5/src/components/Changelog/versions/v0130.md index 90bb459b4..52a440c56 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0130.md +++ b/ui/v2.5/src/components/Changelog/versions/v0130.md @@ -1,5 +1,6 @@ ### 🎨 Improvements -Show counts on list tabs in Performer, Studio and Tag pages. ([#2169](https://github.com/stashapp/stash/pull/2169)) +* Add button to test credentials when adding/editing stash-box endpoints. ([#2173](https://github.com/stashapp/stash/pull/2173)) +* Show counts on list tabs in Performer, Studio and Tag pages. ([#2169](https://github.com/stashapp/stash/pull/2169)) ### 🐛 Bug fixes * Generate sprites for short video files. ([#2167](https://github.com/stashapp/stash/pull/2167)) diff --git a/ui/v2.5/src/components/Settings/StashBoxConfiguration.tsx b/ui/v2.5/src/components/Settings/StashBoxConfiguration.tsx index 5adcabd29..9d0c4cfd1 100644 --- a/ui/v2.5/src/components/Settings/StashBoxConfiguration.tsx +++ b/ui/v2.5/src/components/Settings/StashBoxConfiguration.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useRef, useState } from "react"; import { Button, Form } from "react-bootstrap"; import { FormattedMessage, useIntl } from "react-intl"; import { SettingSection } from "./SettingSection"; @@ -12,6 +12,24 @@ export interface IStashBoxModal { export const StashBoxModal: React.FC = ({ value, close }) => { const intl = useIntl(); + const endpoint = useRef(null); + const apiKey = useRef(null); + + const [validate, { data, loading }] = GQL.useValidateStashBoxLazyQuery({ + fetchPolicy: "network-only", + }); + + const handleValidate = () => { + validate({ + variables: { + input: { + endpoint: endpoint.current?.value ?? "", + api_key: apiKey.current?.value ?? "", + name: "test", + }, + }, + }); + }; return ( @@ -52,6 +70,7 @@ export const StashBoxModal: React.FC = ({ value, close }) => { onChange={(e: React.ChangeEvent) => setValue({ ...v!, endpoint: e.currentTarget.value.trim() }) } + ref={endpoint} /> @@ -71,8 +90,30 @@ export const StashBoxModal: React.FC = ({ value, close }) => { onChange={(e: React.ChangeEvent) => setValue({ ...v!, api_key: e.currentTarget.value.trim() }) } + ref={apiKey} /> + + + + {data && ( + + {data.validateStashBoxCredentials?.status} + + )} + )} close={close} From 1714efc92faf9d7a3562d4c4f22009bbc8996a47 Mon Sep 17 00:00:00 2001 From: kermieisinthehouse Date: Mon, 3 Jan 2022 20:09:03 -0800 Subject: [PATCH 10/80] Add Gender Icons to Performers (#2179) --- .../components/Changelog/versions/v0130.md | 1 + .../src/components/Performers/GenderIcon.tsx | 36 +++++++++++++++++++ .../components/Performers/PerformerCard.tsx | 4 +++ .../Performers/PerformerDetails/Performer.tsx | 5 +++ .../PerformerDetailsPanel.tsx | 9 +++-- .../PerformerDetails/PerformerEditPanel.tsx | 2 +- .../PerformerScrapeDialog.tsx | 2 +- ui/v2.5/src/components/Performers/styles.scss | 18 ++++++++++ ui/v2.5/src/components/Shared/GridCard.tsx | 4 ++- .../src/components/Tagger/PerformerModal.tsx | 6 ++-- ui/v2.5/src/locales/en-GB.json | 10 +++++- 11 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 ui/v2.5/src/components/Performers/GenderIcon.tsx diff --git a/ui/v2.5/src/components/Changelog/versions/v0130.md b/ui/v2.5/src/components/Changelog/versions/v0130.md index 52a440c56..3cd68fee0 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0130.md +++ b/ui/v2.5/src/components/Changelog/versions/v0130.md @@ -1,4 +1,5 @@ ### 🎨 Improvements +* Add gender icons to performers. ([#2179](https://github.com/stashapp/stash/pull/2179)) * Add button to test credentials when adding/editing stash-box endpoints. ([#2173](https://github.com/stashapp/stash/pull/2173)) * Show counts on list tabs in Performer, Studio and Tag pages. ([#2169](https://github.com/stashapp/stash/pull/2169)) diff --git a/ui/v2.5/src/components/Performers/GenderIcon.tsx b/ui/v2.5/src/components/Performers/GenderIcon.tsx new file mode 100644 index 000000000..8f0d788c7 --- /dev/null +++ b/ui/v2.5/src/components/Performers/GenderIcon.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { + faVenus, + faTransgenderAlt, + faMars, +} from "@fortawesome/free-solid-svg-icons"; +import * as GQL from "src/core/generated-graphql"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useIntl } from "react-intl"; + +interface IIconProps { + gender?: GQL.Maybe; + className?: string; +} + +const GenderIcon: React.FC = ({ gender, className }) => { + const intl = useIntl(); + if (gender) { + const icon = + gender === GQL.GenderEnum.Male + ? faMars + : gender === GQL.GenderEnum.Female + ? faVenus + : faTransgenderAlt; + return ( + + ); + } + return null; +}; + +export default GenderIcon; diff --git a/ui/v2.5/src/components/Performers/PerformerCard.tsx b/ui/v2.5/src/components/Performers/PerformerCard.tsx index 619511e70..115ba8363 100644 --- a/ui/v2.5/src/components/Performers/PerformerCard.tsx +++ b/ui/v2.5/src/components/Performers/PerformerCard.tsx @@ -16,6 +16,7 @@ import { CriterionValue, } from "src/models/list-filter/criteria/criterion"; import { PopoverCountButton } from "../Shared/PopoverCountButton"; +import GenderIcon from "./GenderIcon"; export interface IPerformerCardExtraCriteria { scenes: Criterion[]; @@ -183,6 +184,9 @@ export const PerformerCard: React.FC = ({ + } title={performer.name ?? ""} image={ <> diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx index 94f69a1cd..38bbc9ef8 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx @@ -27,6 +27,7 @@ import { PerformerGalleriesPanel } from "./PerformerGalleriesPanel"; import { PerformerMoviesPanel } from "./PerformerMoviesPanel"; import { PerformerImagesPanel } from "./PerformerImagesPanel"; import { PerformerEditPanel } from "./PerformerEditPanel"; +import GenderIcon from "../GenderIcon"; interface IProps { performer: GQL.PerformerDataFragment; @@ -357,6 +358,10 @@ const PerformerPage: React.FC = ({ performer }) => {

+ {performer.name} {renderIcons()} diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx index 03223a9c3..75c754142 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx @@ -4,7 +4,6 @@ import { TagLink } from "src/components/Shared"; import * as GQL from "src/core/generated-graphql"; import { TextUtils } from "src/utils"; import { TextField, URLField } from "src/utils/field"; -import { genderToString } from "src/utils/gender"; interface IPerformerDetails { performer: GQL.PerformerDataFragment; @@ -97,8 +96,12 @@ export const PerformerDetailsPanel: React.FC = ({ return (
= ({ - + = ( onChange={(value) => setAliases(value)} /> {renderScrapedGenderRow( - intl.formatMessage({ id: "gender" }), + intl.formatMessage({ id: "gender.gender" }), gender, (value) => setGender(value) )} diff --git a/ui/v2.5/src/components/Performers/styles.scss b/ui/v2.5/src/components/Performers/styles.scss index 122fdd6f1..6ca209092 100644 --- a/ui/v2.5/src/components/Performers/styles.scss +++ b/ui/v2.5/src/components/Performers/styles.scss @@ -114,3 +114,21 @@ } } } + +.flex-aligned { + align-items: center; + column-gap: 0.5rem; + display: flex; +} + +.fa-mars { + color: #89cff0; +} + +.fa-venus { + color: #f38cac; +} + +.fa-transgender-alt { + color: #c8a2c8; +} diff --git a/ui/v2.5/src/components/Shared/GridCard.tsx b/ui/v2.5/src/components/Shared/GridCard.tsx index f00440007..03974fd32 100644 --- a/ui/v2.5/src/components/Shared/GridCard.tsx +++ b/ui/v2.5/src/components/Shared/GridCard.tsx @@ -9,6 +9,7 @@ interface ICardProps { linkClassName?: string; thumbnailSectionClassName?: string; url: string; + pretitleIcon?: JSX.Element; title: string; image: JSX.Element; details?: JSX.Element; @@ -106,7 +107,8 @@ export const GridCard: React.FC = (props: ICardProps) => { {maybeRenderInteractiveHeatmap()}
-
+
+ {props.pretitleIcon}
diff --git a/ui/v2.5/src/components/Tagger/PerformerModal.tsx b/ui/v2.5/src/components/Tagger/PerformerModal.tsx index 31d98d3ff..79a5d80e6 100755 --- a/ui/v2.5/src/components/Tagger/PerformerModal.tsx +++ b/ui/v2.5/src/components/Tagger/PerformerModal.tsx @@ -11,7 +11,7 @@ import { TruncatedText, } from "src/components/Shared"; import * as GQL from "src/core/generated-graphql"; -import { genderToString, stringToGender } from "src/utils/gender"; +import { stringToGender } from "src/utils/gender"; interface IPerformerModalProps { performer: GQL.ScrapedScenePerformerDataFragment; @@ -191,7 +191,9 @@ const PerformerModal: React.FC = ({ {renderField("aliases", performer.aliases)} {renderField( "gender", - performer.gender ? genderToString(performer.gender) : "" + performer.gender + ? intl.formatMessage({ id: "gender." + performer.gender }) + : "" )} {renderField("birthdate", performer.birthdate)} {renderField("death_date", performer.death_date)} diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index f94e5689a..2f0b8e150 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -691,7 +691,15 @@ "galleries": "Galleries", "gallery": "Gallery", "gallery_count": "Gallery Count", - "gender": "Gender", + "gender": { + "gender": "Gender", + "MALE": "Male", + "FEMALE": "Female", + "TRANSGENDER_MALE": "Transgender Male", + "TRANSGENDER_FEMALE": "Transgender Female", + "INTERSEX": "Intersex", + "NON_BINARY": "Non-Binary" + }, "hair_color": "Hair Colour", "hasMarkers": "Has Markers", "height": "Height", From be5dc7e545688a5eb537838a1df841263c8711f0 Mon Sep 17 00:00:00 2001 From: bnkai <48220860+bnkai@users.noreply.github.com> Date: Tue, 4 Jan 2022 06:47:39 +0200 Subject: [PATCH 11/80] Resolve hostname for chromium RDP requests (#2174) --- pkg/scraper/url.go | 32 +++++++++++++++++-- .../components/Changelog/versions/v0130.md | 1 + 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/pkg/scraper/url.go b/pkg/scraper/url.go index 10a160f40..ddc63a8fb 100644 --- a/pkg/scraper/url.go +++ b/pkg/scraper/url.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "net" "net/http" "net/url" "os" @@ -87,7 +88,7 @@ func loadURL(ctx context.Context, loadURL string, client *http.Client, scraperCo // func urlFromCDP uses chrome cdp and DOM to load and process the url // if remote is set as true in the scraperConfig it will try to use localhost:9222 // else it will look for google-chrome in path -func urlFromCDP(ctx context.Context, url string, driverOptions scraperDriverOptions, globalConfig GlobalConfig) (io.Reader, error) { +func urlFromCDP(ctx context.Context, urlCDP string, driverOptions scraperDriverOptions, globalConfig GlobalConfig) (io.Reader, error) { if !driverOptions.UseCDP { return nil, fmt.Errorf("url shouldn't be fetched through CDP") @@ -107,6 +108,33 @@ func urlFromCDP(ctx context.Context, url string, driverOptions scraperDriverOpti if isCDPPathHTTP(globalConfig) || isCDPPathWS(globalConfig) { remote := cdpPath + // ------------------------------------------------------------------- + // #1023 + // when chromium is listening over RDP it only accepts requests + // with host headers that are either IPs or `localhost` + cdpURL, err := url.Parse(remote) + if err != nil { + return nil, fmt.Errorf("failed to parse CDP Path: %v", err) + } + hostname := cdpURL.Hostname() + if hostname != "localhost" { + if net.ParseIP(hostname) == nil { // not an IP + addr, err := net.LookupIP(hostname) + if err != nil || len(addr) == 0 { // can not resolve to IP + return nil, fmt.Errorf("CDP: hostname <%s> can not be resolved", hostname) + } + if len(addr[0]) == 0 { // nil IP + return nil, fmt.Errorf("CDP: hostname <%s> resolved to nil", hostname) + } + // addr is a valid IP + // replace the host part of the cdpURL with the IP + cdpURL.Host = strings.Replace(cdpURL.Host, hostname, addr[0].String(), 1) + // use that for remote + remote = cdpURL.String() + } + } + // -------------------------------------------------------------------- + // if CDPPath is http(s) then we need to get the websocket URL if isCDPPathHTTP(globalConfig) { var err error @@ -150,7 +178,7 @@ func urlFromCDP(ctx context.Context, url string, driverOptions scraperDriverOpti setCDPCookies(driverOptions), printCDPCookies(driverOptions, "Cookies found"), network.SetExtraHTTPHeaders(network.Headers(headers)), - chromedp.Navigate(url), + chromedp.Navigate(urlCDP), chromedp.Sleep(sleepDuration), setCDPClicks(driverOptions), chromedp.OuterHTML("html", &res, chromedp.ByQuery), diff --git a/ui/v2.5/src/components/Changelog/versions/v0130.md b/ui/v2.5/src/components/Changelog/versions/v0130.md index 3cd68fee0..f0276b26b 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0130.md +++ b/ui/v2.5/src/components/Changelog/versions/v0130.md @@ -4,6 +4,7 @@ * Show counts on list tabs in Performer, Studio and Tag pages. ([#2169](https://github.com/stashapp/stash/pull/2169)) ### 🐛 Bug fixes +* Resolve CDP hostname if necessary. ([#2174](https://github.com/stashapp/stash/pull/2174)) * Generate sprites for short video files. ([#2167](https://github.com/stashapp/stash/pull/2167)) * Fix stash-box scraping including underscores in ethnicity. ([#2191](https://github.com/stashapp/stash/pull/2191)) * Fix stash-box batch performer task not setting birthdate. ([#2189](https://github.com/stashapp/stash/pull/2189)) From baf148625cae634db2fe6b81c5c85c0f08e657aa Mon Sep 17 00:00:00 2001 From: kermieisinthehouse Date: Tue, 4 Jan 2022 19:18:57 -0800 Subject: [PATCH 12/80] Touch up Performer Page (#2200) * Moves "Edit" and "Autotag" out of performer tabs * Smoothen out fedit submission behavior --- .../components/Changelog/versions/v0130.md | 1 + .../Movies/MovieDetails/MovieEditPanel.tsx | 2 +- .../src/components/Performers/GenderIcon.tsx | 2 +- .../Performers/PerformerDetails/Performer.tsx | 193 ++++++++++-------- .../PerformerDetailsPanel.tsx | 4 +- .../PerformerDetails/PerformerEditPanel.tsx | 100 ++++----- .../PerformerOperationsPanel.tsx | 34 --- .../PerformerScrapeDialog.tsx | 2 +- .../Studios/StudioDetails/StudioEditPanel.tsx | 2 +- .../src/components/Tagger/PerformerModal.tsx | 2 +- .../Tags/TagDetails/TagEditPanel.tsx | 2 +- ui/v2.5/src/locales/en-GB.json | 4 +- 12 files changed, 160 insertions(+), 188 deletions(-) delete mode 100644 ui/v2.5/src/components/Performers/PerformerDetails/PerformerOperationsPanel.tsx diff --git a/ui/v2.5/src/components/Changelog/versions/v0130.md b/ui/v2.5/src/components/Changelog/versions/v0130.md index f0276b26b..a389b43cd 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0130.md +++ b/ui/v2.5/src/components/Changelog/versions/v0130.md @@ -1,4 +1,5 @@ ### 🎨 Improvements +* Made Performer page consistent with Studio and Tag pages. ([#2200](https://github.com/stashapp/stash/pull/2200)) * Add gender icons to performers. ([#2179](https://github.com/stashapp/stash/pull/2179)) * Add button to test credentials when adding/editing stash-box endpoints. ([#2173](https://github.com/stashapp/stash/pull/2173)) * Show counts on list tabs in Performer, Studio and Tag pages. ([#2169](https://github.com/stashapp/stash/pull/2169)) diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx index e07e8bf3c..64d70b3e1 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx @@ -470,7 +470,7 @@ export const MovieEditPanel: React.FC = ({ = ({ gender, className }) => { : faTransgenderAlt; return ( diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx index 38bbc9ef8..a2c12c3d6 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState } from "react"; -import { Button, Tabs, Tab, Badge } from "react-bootstrap"; +import { Button, Tabs, Tab, Badge, Col, Row } from "react-bootstrap"; import { FormattedMessage, useIntl } from "react-intl"; import { useParams, useHistory } from "react-router-dom"; import { Helmet } from "react-helmet"; @@ -10,9 +10,11 @@ import { useFindPerformer, usePerformerUpdate, usePerformerDestroy, + mutateMetadataAutoTag, } from "src/core/StashService"; import { CountryFlag, + DetailsEditNavbar, ErrorMessage, Icon, LoadingIndicator, @@ -21,7 +23,6 @@ import { useLightbox, useToast } from "src/hooks"; import { TextUtils } from "src/utils"; import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars"; import { PerformerDetailsPanel } from "./PerformerDetailsPanel"; -import { PerformerOperationsPanel } from "./PerformerOperationsPanel"; import { PerformerScenesPanel } from "./PerformerScenesPanel"; import { PerformerGalleriesPanel } from "./PerformerGalleriesPanel"; import { PerformerMoviesPanel } from "./PerformerMoviesPanel"; @@ -44,6 +45,7 @@ const PerformerPage: React.FC = ({ performer }) => { const [imagePreview, setImagePreview] = useState(); const [imageEncoding, setImageEncoding] = useState(false); + const [isEditing, setIsEditing] = useState(false); // if undefined then get the existing image // if null then get the default (no) image @@ -68,9 +70,7 @@ const PerformerPage: React.FC = ({ performer }) => { tab === "scenes" || tab === "galleries" || tab === "images" || - tab === "movies" || - tab === "edit" || - tab === "operations" + tab === "movies" ? tab : "details"; const setActiveTabKey = (newTab: string | null) => { @@ -84,14 +84,24 @@ const PerformerPage: React.FC = ({ performer }) => { const onImageEncoding = (isEncoding = false) => setImageEncoding(isEncoding); + async function onAutoTag() { + try { + await mutateMetadataAutoTag({ performers: [performer.id] }); + Toast.success({ + content: intl.formatMessage({ id: "toast.started_auto_tagging" }), + }); + } catch (e) { + Toast.error(e); + } + } + // set up hotkeys useEffect(() => { Mousetrap.bind("a", () => setActiveTabKey("details")); - Mousetrap.bind("e", () => setActiveTabKey("edit")); + Mousetrap.bind("e", () => setIsEditing(!isEditing)); Mousetrap.bind("c", () => setActiveTabKey("scenes")); Mousetrap.bind("g", () => setActiveTabKey("galleries")); Mousetrap.bind("m", () => setActiveTabKey("movies")); - Mousetrap.bind("o", () => setActiveTabKey("operations")); Mousetrap.bind("f", () => setFavorite(!performer.favorite)); // numeric keypresses get caught by jwplayer, so blur the element @@ -139,85 +149,108 @@ const PerformerPage: React.FC = ({ performer }) => { } const renderTabs = () => ( - - - - - - {intl.formatMessage({ id: "scenes" })} - - {intl.formatNumber(performer.scene_count ?? 0)} - - - } + + + + { + setIsEditing(!isEditing); + }} + onDelete={onDelete} + onAutoTag={onAutoTag} + isNew={false} + isEditing={false} + onSave={() => {}} + onImageChange={() => {}} + /> + + + - - - - {intl.formatMessage({ id: "galleries" })} - - {intl.formatNumber(performer.gallery_count ?? 0)} - - - } - > - - - - {intl.formatMessage({ id: "images" })} - - {intl.formatNumber(performer.image_count ?? 0)} - - - } - > - - - - {intl.formatMessage({ id: "movies" })} - - {intl.formatNumber(performer.movie_count ?? 0)} - - - } - > - - - + + + + + {intl.formatMessage({ id: "scenes" })} + + {intl.formatNumber(performer.scene_count ?? 0)} + + + } + > + + + + {intl.formatMessage({ id: "galleries" })} + + {intl.formatNumber(performer.gallery_count ?? 0)} + + + } + > + + + + {intl.formatMessage({ id: "images" })} + + {intl.formatNumber(performer.image_count ?? 0)} + + + } + > + + + + {intl.formatMessage({ id: "movies" })} + + {intl.formatNumber(performer.movie_count ?? 0)} + + + } + > + + + + + ); + + function renderTabsOrEditPanel() { + if (isEditing) { + return ( { + setIsEditing(false); + }} /> - - - - - - ); + ); + } else { + return renderTabs(); + } + } function maybeRenderAge() { if (performer?.birthdate) { @@ -375,7 +408,7 @@ const PerformerPage: React.FC = ({ performer }) => {

-
{renderTabs()}
+
{renderTabsOrEditPanel()}
diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx index 75c754142..2c7c16ff3 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx @@ -96,10 +96,10 @@ export const PerformerDetailsPanel: React.FC = ({ return (
diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx index 1cd525e27..99fd8c5bc 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import { Button, Form, Col, Row, Badge, Dropdown } from "react-bootstrap"; -import { FormattedMessage, useIntl } from "react-intl"; +import { FormattedMessage } from "react-intl"; import Mousetrap from "mousetrap"; import * as GQL from "src/core/generated-graphql"; import * as yup from "yup"; @@ -18,7 +18,6 @@ import { ImageInput, LoadingIndicator, CollapseButton, - Modal, TagSelect, URLField, } from "src/components/Shared"; @@ -46,20 +45,19 @@ interface IPerformerDetails { performer: Partial; isNew?: boolean; isVisible: boolean; - onDelete?: () => void; onImageChange?: (image?: string | null) => void; onImageEncoding?: (loading?: boolean) => void; + onCancelEditing?: () => void; } export const PerformerEditPanel: React.FC = ({ performer, isNew, isVisible, - onDelete, onImageChange, onImageEncoding, + onCancelEditing, }) => { - const intl = useIntl(); const Toast = useToast(); const history = useHistory(); @@ -67,7 +65,6 @@ export const PerformerEditPanel: React.FC = ({ const [scraper, setScraper] = useState(); const [newTags, setNewTags] = useState(); const [isScraperModalOpen, setIsScraperModalOpen] = useState(false); - const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); // Network state const [isLoading, setIsLoading] = useState(false); @@ -361,7 +358,17 @@ export const PerformerEditPanel: React.FC = ({ async function onSave(performerInput: InputValues) { setIsLoading(true); try { - if (!isNew) { + if (isNew) { + const input = getCreateValues(performerInput); + const result = await createPerformer({ + variables: { + input, + }, + }); + if (result.data?.performerCreate) { + history.push(`/performers/${result.data.performerCreate.id}`); + } + } else { const input = getUpdateValues(performerInput); await updatePerformer({ @@ -372,20 +379,14 @@ export const PerformerEditPanel: React.FC = ({ }, }, }); - history.push(`/performers/${performer.id}`); - } else { - const input = getCreateValues(performerInput); - const result = await createPerformer({ - variables: { - input, - }, - }); - if (result.data?.performerCreate) { - history.push(`/performers/${result.data.performerCreate.id}`); - } } } catch (e) { Toast.error(e); + setIsLoading(false); + return; + } + if (!isNew && onCancelEditing) { + onCancelEditing(); } setIsLoading(false); } @@ -397,12 +398,6 @@ export const PerformerEditPanel: React.FC = ({ onSave?.(formik.values); }); - if (!isNew) { - Mousetrap.bind("d d", () => { - setIsDeleteAlertOpen(true); - }); - } - return () => { Mousetrap.unbind("s s"); @@ -655,25 +650,17 @@ export const PerformerEditPanel: React.FC = ({ setScraper(undefined); } - function renderButtons() { + function renderButtons(classNames: string) { return ( - - - {!isNew ? ( + + {!isNew && onCancelEditing ? ( ) : ( "" @@ -685,12 +672,19 @@ export const PerformerEditPanel: React.FC = ({ onImageURL={onImageChangeURL} /> + ); @@ -716,28 +710,6 @@ export const PerformerEditPanel: React.FC = ({ ) : undefined; }; - function renderDeleteAlert() { - return ( - setIsDeleteAlertOpen(false) }} - > -

- -

-
- ); - } - function renderTagsField() { return ( @@ -837,7 +809,6 @@ export const PerformerEditPanel: React.FC = ({ return ( <> - {renderDeleteAlert()} {renderScrapeModal()} {maybeRenderScrapeDialog()} @@ -845,6 +816,7 @@ export const PerformerEditPanel: React.FC = ({ when={formik.dirty} message="Unsaved changes. Are you sure you want to leave?" /> + {renderButtons("mb-3")}
@@ -880,7 +852,7 @@ export const PerformerEditPanel: React.FC = ({ - + = ({ {renderStashIDs()} - {renderButtons()} + {renderButtons("mt-3")} ); diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerOperationsPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerOperationsPanel.tsx deleted file mode 100644 index 29723be54..000000000 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerOperationsPanel.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Button } from "react-bootstrap"; -import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; -import * as GQL from "src/core/generated-graphql"; -import { mutateMetadataAutoTag } from "src/core/StashService"; -import { useToast } from "src/hooks"; - -interface IPerformerOperationsProps { - performer: GQL.PerformerDataFragment; -} - -export const PerformerOperationsPanel: React.FC = ({ - performer, -}) => { - const Toast = useToast(); - const intl = useIntl(); - - async function onAutoTag() { - try { - await mutateMetadataAutoTag({ performers: [performer.id] }); - Toast.success({ - content: intl.formatMessage({ id: "toast.started_auto_tagging" }), - }); - } catch (e) { - Toast.error(e); - } - } - - return ( - - ); -}; diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScrapeDialog.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScrapeDialog.tsx index 4ac91ec8b..9225cb976 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScrapeDialog.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScrapeDialog.tsx @@ -429,7 +429,7 @@ export const PerformerScrapeDialog: React.FC = ( onChange={(value) => setAliases(value)} /> {renderScrapedGenderRow( - intl.formatMessage({ id: "gender.gender" }), + intl.formatMessage({ id: "gender" }), gender, (value) => setGender(value) )} diff --git a/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx b/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx index fa793fa12..fa7af6832 100644 --- a/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx +++ b/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx @@ -318,7 +318,7 @@ export const StudioEditPanel: React.FC = ({ = ({ {renderField( "gender", performer.gender - ? intl.formatMessage({ id: "gender." + performer.gender }) + ? intl.formatMessage({ id: "gender_types." + performer.gender }) : "" )} {renderField("birthdate", performer.birthdate)} diff --git a/ui/v2.5/src/components/Tags/TagDetails/TagEditPanel.tsx b/ui/v2.5/src/components/Tags/TagDetails/TagEditPanel.tsx index 379550037..a59e8cea9 100644 --- a/ui/v2.5/src/components/Tags/TagDetails/TagEditPanel.tsx +++ b/ui/v2.5/src/components/Tags/TagDetails/TagEditPanel.tsx @@ -214,7 +214,7 @@ export const TagEditPanel: React.FC = ({ Date: Tue, 4 Jan 2022 19:21:15 -0800 Subject: [PATCH 13/80] Add preview languages (#2194) * Add preview languages * Turkish is completed * Update README.md --- README.md | 4 ++-- pkg/api/locale.go | 4 ++++ .../SettingsInterfacePanel.tsx | 20 +++++++++++-------- ui/v2.5/src/locales/index.ts | 8 ++++++++ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 385cccecc..470801b38 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,9 @@ The simplest way to tag a large number of files is by using the [Tagger](https:/ # Translation [![Translate](https://translate.stashapp.cc/widgets/stash/-/stash-desktop-client/svg-badge.svg)](https://translate.stashapp.cc/engage/stash/) -🇧🇷 🇨🇳 🇬🇧 🇫🇮 🇫🇷 🇩🇪 🇮🇹 🇪🇸 🇸🇪 🇹🇼 +🇧🇷 🇨🇳 🇬🇧 🇫🇮 🇫🇷 🇩🇪 🇮🇹 🇪🇸 🇸🇪 🇹🇼 🇹🇷 -Stash is available in 10 languages (so far!) and it could be in your language too. If you want to help us translate Stash into your language, you can make an account at [translate.stashapp.cc](https://translate.stashapp.cc/projects/stash/stash-desktop-client/) to get started contributing new languages or improving existing ones. Thanks! +Stash is available in 11 languages (so far!) and it could be in your language too. If you want to help us translate Stash into your language, you can make an account at [translate.stashapp.cc](https://translate.stashapp.cc/projects/stash/stash-desktop-client/) to get started contributing new languages or improving existing ones. Thanks! # Support (FAQ) diff --git a/pkg/api/locale.go b/pkg/api/locale.go index f5f389ee7..23381b112 100644 --- a/pkg/api/locale.go +++ b/pkg/api/locale.go @@ -19,6 +19,10 @@ var matcher = language.NewMatcher([]language.Tag{ language.MustParse("sv-SE"), language.MustParse("zh-CN"), language.MustParse("zh-TW"), + language.MustParse("hr-HR"), + language.MustParse("nl-NL"), + language.MustParse("ru-RU"), + language.MustParse("tr-TR"), }) // newCollator parses a locale into a collator diff --git a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx index 27bf5cc69..a512a0743 100644 --- a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx @@ -44,15 +44,19 @@ export const SettingsInterfacePanel: React.FC = () => { value={iface.language ?? undefined} onChange={(v) => saveInterface({ language: v })} > - + - - - - - - - + + + + + + + + + + + diff --git a/ui/v2.5/src/locales/index.ts b/ui/v2.5/src/locales/index.ts index 89da63cd0..64d6c650b 100644 --- a/ui/v2.5/src/locales/index.ts +++ b/ui/v2.5/src/locales/index.ts @@ -9,6 +9,10 @@ import fiFI from "./fi-FI.json"; import svSE from "./sv-SE.json"; import zhTW from "./zh-TW.json"; import zhCN from "./zh-CN.json"; +import hrHR from "./hr-HR.json"; +import nlNL from "./nl-NL.json"; +import ruRU from "./ru-RU.json"; +import trTR from "./tr-TR.json"; export default { deDE, @@ -22,4 +26,8 @@ export default { svSE, zhTW, zhCN, + hrHR, + nlNL, + ruRU, + trTR, }; From 28c72d3ee3a9f5d270eb44bf1133cbd998247d3a Mon Sep 17 00:00:00 2001 From: kermieisinthehouse Date: Mon, 10 Jan 2022 15:05:12 -0800 Subject: [PATCH 14/80] Allow stash to be iframed (#2217) --- pkg/api/server.go | 1 - ui/v2.5/src/components/Changelog/versions/v0130.md | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/server.go b/pkg/api/server.go index fb07861c7..c06a24120 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -364,7 +364,6 @@ func SecurityHeadersMiddleware(next http.Handler) http.Handler { w.Header().Set("Referrer-Policy", "same-origin") w.Header().Set("X-Content-Type-Options", "nosniff") - w.Header().Set("X-Frame-Options", "DENY") w.Header().Set("X-XSS-Protection", "1") w.Header().Set("Content-Security-Policy", cspDirectives) diff --git a/ui/v2.5/src/components/Changelog/versions/v0130.md b/ui/v2.5/src/components/Changelog/versions/v0130.md index a389b43cd..38bf5ca21 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0130.md +++ b/ui/v2.5/src/components/Changelog/versions/v0130.md @@ -5,6 +5,7 @@ * Show counts on list tabs in Performer, Studio and Tag pages. ([#2169](https://github.com/stashapp/stash/pull/2169)) ### 🐛 Bug fixes +* Allow Stash to be iframed. ([#2217](https://github.com/stashapp/stash/pull/2217)) * Resolve CDP hostname if necessary. ([#2174](https://github.com/stashapp/stash/pull/2174)) * Generate sprites for short video files. ([#2167](https://github.com/stashapp/stash/pull/2167)) * Fix stash-box scraping including underscores in ethnicity. ([#2191](https://github.com/stashapp/stash/pull/2191)) From e1cf695b65bf9f7b490b24dc42aaab5dd75ff4d9 Mon Sep 17 00:00:00 2001 From: kermieisinthehouse Date: Mon, 10 Jan 2022 15:09:14 -0800 Subject: [PATCH 15/80] Deprecate forwarded port (#2228) --- pkg/api/server.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pkg/api/server.go b/pkg/api/server.go index c06a24120..e92c3824f 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -384,13 +384,7 @@ func BaseURLMiddleware(next http.Handler) http.Handler { } prefix := getProxyPrefix(r.Header) - port := "" - forwardedPort := r.Header.Get("X-Forwarded-Port") - if forwardedPort != "" && forwardedPort != "80" && forwardedPort != "8080" && forwardedPort != "443" && !strings.Contains(r.Host, ":") { - port = ":" + forwardedPort - } - - baseURL := scheme + "://" + r.Host + port + prefix + baseURL := scheme + "://" + r.Host + prefix externalHost := config.GetInstance().GetExternalHost() if externalHost != "" { From df1478c25d5772345b8f5ba37b4cabe2decdb2ce Mon Sep 17 00:00:00 2001 From: InfiniteTF Date: Tue, 11 Jan 2022 00:20:50 +0100 Subject: [PATCH 16/80] Only show one modal when tagging performers (#2223) --- ui/v2.5/src/components/Tagger/performers/PerformerTagger.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/v2.5/src/components/Tagger/performers/PerformerTagger.tsx b/ui/v2.5/src/components/Tagger/performers/PerformerTagger.tsx index 46f22e588..a8fac4f97 100755 --- a/ui/v2.5/src/components/Tagger/performers/PerformerTagger.tsx +++ b/ui/v2.5/src/components/Tagger/performers/PerformerTagger.tsx @@ -335,7 +335,7 @@ const PerformerTaggerList: React.FC = ({ {modalPerformer && ( setModalPerformer(undefined)} - modalVisible={modalPerformer !== undefined} + modalVisible={modalPerformer.stored_id === performer.id} performer={modalPerformer} onSave={handlePerformerUpdate} excludedPerformerFields={config.excludedPerformerFields} From b527ac5d1ff08fce29a2673364d86946ddc28a05 Mon Sep 17 00:00:00 2001 From: InfiniteTF Date: Tue, 11 Jan 2022 00:23:29 +0100 Subject: [PATCH 17/80] Fix performer tagger gender setting (#2222) --- ui/v2.5/src/utils/gender.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/v2.5/src/utils/gender.ts b/ui/v2.5/src/utils/gender.ts index 1a1934468..b68faf4ef 100644 --- a/ui/v2.5/src/utils/gender.ts +++ b/ui/v2.5/src/utils/gender.ts @@ -26,11 +26,14 @@ export const genderToString = (value?: GQL.GenderEnum | string) => { export const stringToGender = ( value?: string | null, caseInsensitive?: boolean -) => { +): GQL.GenderEnum | undefined => { if (!value) { return undefined; } + const existing = Object.entries(GQL.GenderEnum).find((e) => e[1] === value); + if (existing) return existing[1]; + const ret = stringGenderMap.get(value); if (ret || !caseInsensitive) { return ret; From 20999cf65bc8a3ee83b2fb30ab96693e80fd98b5 Mon Sep 17 00:00:00 2001 From: stg-annon <14135675+stg-annon@users.noreply.github.com> Date: Mon, 10 Jan 2022 18:24:51 -0500 Subject: [PATCH 18/80] add link to Scene Filename Parser docs (#2212) --- ui/v2.5/src/docs/en/SceneFilenameParser.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/v2.5/src/docs/en/SceneFilenameParser.md b/ui/v2.5/src/docs/en/SceneFilenameParser.md index 01da463fa..710bebcac 100644 --- a/ui/v2.5/src/docs/en/SceneFilenameParser.md +++ b/ui/v2.5/src/docs/en/SceneFilenameParser.md @@ -1,6 +1,6 @@ # Scene Filename Parser -This tool parses the scene filenames in your library and allows setting the metadata from those filenames. +[This tool](/sceneFilenameParser) parses the scene filenames in your library and allows setting the metadata from those filenames. ## Parser Options From 7a0aa5d94a43a559b702f9395c74df83f7536cac Mon Sep 17 00:00:00 2001 From: stash-translation-bot <94573628+stash-translation-bot@users.noreply.github.com> Date: Mon, 10 Jan 2022 15:30:33 -0800 Subject: [PATCH 19/80] Translations update from Stash (#2181) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Russian) Currently translated at 14.6% (106 of 723 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ru/ * Translated using Weblate (German) Currently translated at 99.5% (720 of 723 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/ * Translated using Weblate (Dutch) Currently translated at 38.4% (278 of 723 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/nl/ * Translated using Weblate (Finnish) Currently translated at 91.4% (661 of 723 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/ * Translated using Weblate (Russian) Currently translated at 37.8% (274 of 723 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ru/ * Translated using Weblate (Spanish) Currently translated at 95.5% (691 of 723 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/ * Translated using Weblate (German) Currently translated at 100.0% (723 of 723 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/ * Translated using Weblate (German) Currently translated at 100.0% (723 of 723 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/ * Translated using Weblate (Turkish) Currently translated at 100.0% (723 of 723 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/tr/ * Added translation using Weblate (Danish) * Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (729 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/ * Translated using Weblate (Danish) Currently translated at 3.0% (22 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/da/ * Translated using Weblate (German) Currently translated at 100.0% (729 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/ * Translated using Weblate (Finnish) Currently translated at 95.4% (696 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/ * Translated using Weblate (Danish) Currently translated at 16.0% (117 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/da/ * Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ * Translated using Weblate (French) Currently translated at 96.4% (703 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/ * Translated using Weblate (German) Currently translated at 100.0% (729 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/ * Translated using Weblate (Italian) Currently translated at 100.0% (729 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/ * Translated using Weblate (German) Currently translated at 100.0% (729 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/ * Translated using Weblate (German) Currently translated at 100.0% (729 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/ * Translated using Weblate (Russian) Currently translated at 37.7% (275 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ru/ * Translated using Weblate (Dutch) Currently translated at 78.6% (573 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/nl/ * Translated using Weblate (Spanish) Currently translated at 95.1% (694 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/ * Translated using Weblate (Dutch) Currently translated at 83.6% (610 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/nl/ * Translated using Weblate (Swedish) Currently translated at 100.0% (729 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/ * Translated using Weblate (Dutch) Currently translated at 100.0% (729 of 729 strings) Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/nl/ * Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Stash/Stash Desktop Client Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ Co-authored-by: pixeldonut Co-authored-by: Niemand Co-authored-by: irazox Co-authored-by: Aa Co-authored-by: failead0r <6@example.com> Co-authored-by: Weblate Co-authored-by: phasetime <4@example.com> Co-authored-by: UncleRoger33 Co-authored-by: Hidden Hiddenson <078emil@protonmail.com> Co-authored-by: Still Co-authored-by: Stashing OpenSource Co-authored-by: BViking78 <5@example.com> Co-authored-by: Christoph Mühlbacher Co-authored-by: greatcb12 Co-authored-by: Kermie Co-authored-by: Alpaca Serious --- ui/v2.5/src/locales/da-DK.json | 128 ++++++ ui/v2.5/src/locales/de-DE.json | 78 ++-- ui/v2.5/src/locales/es-ES.json | 14 +- ui/v2.5/src/locales/fi-FI.json | 84 +++- ui/v2.5/src/locales/fr-FR.json | 17 + ui/v2.5/src/locales/it-IT.json | 8 + ui/v2.5/src/locales/nl-NL.json | 594 ++++++++++++++++++++++++++- ui/v2.5/src/locales/pt-BR.json | 1 - ui/v2.5/src/locales/ru-RU.json | 323 ++++++++++++++- ui/v2.5/src/locales/sv-SE.json | 8 + ui/v2.5/src/locales/tr-TR.json | 731 +++++++++++++++++++++++++++++++-- ui/v2.5/src/locales/zh-CN.json | 1 - ui/v2.5/src/locales/zh-TW.json | 4 +- 13 files changed, 1891 insertions(+), 100 deletions(-) create mode 100644 ui/v2.5/src/locales/da-DK.json diff --git a/ui/v2.5/src/locales/da-DK.json b/ui/v2.5/src/locales/da-DK.json new file mode 100644 index 000000000..c0d3fb58b --- /dev/null +++ b/ui/v2.5/src/locales/da-DK.json @@ -0,0 +1,128 @@ +{ + "actions": { + "add": "Tilføj", + "add_directory": "Tilføj mappe", + "add_entity": "Tilføj {entityType}", + "add_to_entity": "Tilføj til {entityType}", + "allow": "Tillad", + "allow_temporarily": "Tillad midlertidigt", + "apply": "Anvend", + "auto_tag": "Auto Tagge", + "backup": "Backup", + "browse_for_image": "Vælg billede…", + "cancel": "Afbryd", + "clean": "Rens", + "clear": "Ryd", + "clear_back_image": "Ryd bagerst billede", + "clear_front_image": "Ryd forrest billede", + "clear_image": "Ryd Billede", + "close": "Luk", + "confirm": "Bekræft", + "continue": "Forsæt", + "create": "Lav", + "create_entity": "Lav {entityType}", + "create_marker": "Lav Mærke", + "created_entity": "Lavet {entity_type}: {entity_name}", + "delete": "Slet", + "delete_entity": "Slet {entityType}", + "delete_file": "Slet fil", + "delete_generated_supporting_files": "Slet genereret filer", + "disallow": "Tillad ikke", + "download": "Download", + "download_backup": "Download Backup", + "edit": "Ændre", + "export": "Eksportere…", + "export_all": "Exportere alt…", + "find": "Find", + "finish": "Afslut", + "from_file": "Fra fil…", + "from_url": "Fra URL…", + "full_export": "Fuld Eksport", + "full_import": "Fuld Import", + "generate": "Generer", + "generate_thumb_default": "Generer standard thumbnail", + "generate_thumb_from_current": "Generer thumbnail fra nuværende", + "hash_migration": "hash migration", + "hide": "Skjul", + "identify": "identificer", + "ignore": "Ignorere", + "import": "Importer…", + "import_from_file": "Importer fra fil", + "merge": "Fusioner", + "merge_from": "Fusioner fra", + "merge_into": "Fusioner til", + "next_action": "Næste", + "not_running": "kører ikke", + "open_random": "Åben Tilfældig", + "overwrite": "Overskriv", + "play_random": "Afspil tilfældig", + "play_selected": "Afspil valgte", + "preview": "Forhåndsvisning", + "previous_action": "Tilbage", + "refresh": "Opdater", + "reload_plugins": "Genindlæs plugins", + "reload_scrapers": "Genindlæs scrapers", + "remove": "Fjern", + "rename_gen_files": "Omdøb genereret filer", + "rescan": "Genscan", + "reshuffle": "Bland om", + "running": "kører", + "save": "Gem", + "save_delete_settings": "Brug disse muligheder som standard når der slettes", + "save_filter": "Gem filter", + "scan": "Scan", + "scrape": "Skrabe", + "scrape_query": "Skrab forespørgsel", + "scrape_scene_fragment": "Skrab for fragment", + "scrape_with": "Skrab med…", + "search": "Søg", + "select_all": "Vælg Alt", + "select_folders": "Vælg mappe", + "select_none": "Vælg Ingen", + "selective_auto_tag": "Selektiv Auto Tag", + "selective_clean": "Selektiv Ryd", + "selective_scan": "Selektiv Scan", + "set_as_default": "Vælg som standard", + "set_back_image": "Bagerst billede…", + "set_front_image": "Forrest billede…", + "set_image": "Vælg billede…", + "show": "Vis", + "skip": "Spring over", + "stop": "Stop", + "tasks": { + "clean_confirm_message": "Er du sikker på, at du vil Rense? Dette vil slette databaseinformation og genereret indhold for alle scener og gallerier, der ikke længere findes i filsystemet.", + "dry_mode_selected": "Tør Tilstand valgt. Ingen egentlig sletning vil finde sted, kun logning.", + "import_warning": "Er du sikker på, at du vil importere? Dette vil slette databasen og genimportere fra dine eksporterede metadata." + }, + "temp_disable": "Deaktiver midlertidigt…", + "temp_enable": "Aktiver midlertidigt…", + "use_default": "Brug standard", + "view_random": "Vis Tilfældig" + }, + "actions_name": "Handlinger", + "age": "Alder", + "aliases": "Aliaser", + "all": "alt", + "also_known_as": "Også kendt som", + "ascending": "Stigende", + "average_resolution": "Gennemsnitlig Opløsning", + "birth_year": "Fødselsår", + "birthdate": "Fødselsdato", + "bitrate": "Bithastighed", + "career_length": "Karrierer Længde", + "component_tagger": { + "config": { + "active_instance": "Aktiv stash-box instans:", + "blacklist_desc": "Sortlistet elementer er ekskluderet fra forespørgsler. Bemærk, at de er regulære udtryk og også ufølsomme for store og små bogstaver. Visse tegn skal escapes med en omvendt skråstreg: {chars_require_escape}", + "blacklist_label": "Sortlist", + "query_mode_auto": "Auto", + "query_mode_auto_desc": "Bruger metadata, hvis de er til stede, eller filnavn", + "query_mode_dir": "Mappe", + "query_mode_dir_desc": "Bruger kun overordnet mappe til videofil", + "query_mode_filename": "Filnavn", + "query_mode_filename_desc": "Bruger kun filnavn", + "query_mode_label": "Forespørgselstilstand", + "query_mode_metadata": "Metadata" + } + } +} diff --git a/ui/v2.5/src/locales/de-DE.json b/ui/v2.5/src/locales/de-DE.json index d39d3f102..19344ecd5 100644 --- a/ui/v2.5/src/locales/de-DE.json +++ b/ui/v2.5/src/locales/de-DE.json @@ -21,7 +21,7 @@ "continue": "Fortsetzen", "create": "Erstellen", "create_entity": "Erstelle {entityType}", - "create_marker": "Erstelle Marke", + "create_marker": "Erstelle Markierung", "created_entity": "{entity_type} erstellt: {entity_name}", "delete": "Löschen", "delete_entity": "Lösche {entityType}", @@ -32,7 +32,7 @@ "download_backup": "Lade Backup herunter", "edit": "Bearbeiten", "export": "Exportieren…", - "export_all": "Alles exportieren…", + "export_all": "Alle exportieren…", "find": "Suchen", "finish": "Fertig", "from_file": "Aus Datei…", @@ -55,7 +55,7 @@ "not_running": "wird nicht ausgeführt", "open_random": "Öffne Zufällig", "overwrite": "Überschreiben", - "play_random": "Spiele zufällig ab", + "play_random": "Zufällige Wiedergabe", "play_selected": "Spiele ausgewählte", "preview": "Vorschau", "previous_action": "Zurück", @@ -79,7 +79,7 @@ "select_all": "Alle auswählen", "select_folders": "Ordner auswählen", "select_none": "Nichts auswählen", - "selective_auto_tag": "Automatisch selektiv eti­ket­tie­ren", + "selective_auto_tag": "Automatisch selektiv taggen", "selective_clean": "Selektive Reinigung", "selective_scan": "Selektiv scannen", "set_as_default": "Als Voreinstellung festlegen", @@ -109,7 +109,7 @@ "birth_year": "Geburtsjahr", "birthdate": "Geburtsdatum", "bitrate": "Bitrate", - "career_length": "Länge der Karriere", + "career_length": "Karrierelänge", "component_tagger": { "config": { "active_instance": "Aktive stash-box Instanz:", @@ -121,7 +121,7 @@ "query_mode_dir_desc": "Nutzt nur den übergeordneten Ordner", "query_mode_filename": "Dateiname", "query_mode_filename_desc": "Nutzt nur den Dateinamen", - "query_mode_label": "Anfragemodus", + "query_mode_label": "Suchmodus", "query_mode_metadata": "Metadaten", "query_mode_metadata_desc": "Nutzt nur Metadaten", "query_mode_path": "Pfad", @@ -176,7 +176,7 @@ "about": "Über", "interface": "Oberfläche", "logs": "Protokoll", - "metadata_providers": "Metadaten Anbieter", + "metadata_providers": "Metadaten-Anbieter", "plugins": "Plugins", "scraping": "Durchsuchen", "security": "Sicherheit", @@ -245,7 +245,7 @@ "gallery_ext_head": "Galeriecontainer Dateiformate", "generated_file_naming_hash_desc": "Verwende MD5 oder oshash für die Benennung der generierten Dateien. Um dies zu ändern, müssen für alle Szenen der entsprechende MD5/oshash berechnet werden. Nachdem dieser Wert geändert wurde, müssen vorhandene generierte Dateien migriert oder neu generiert werden. Siehe Aufgabenseite für die Migration.", "generated_file_naming_hash_head": "Dateinamen-Hash für generierte Dateien", - "generated_files_location": "Verzeichnisspeicherort für die generierten Dateien (Szenenmarkierungen, Szenenvorschauen, Sprites usw.)", + "generated_files_location": "Verzeichnisspeicherort für die generierten Dateien (Markierungen, Vorschauen, Sprites usw.)", "generated_path_head": "Pfad für generierte Dateien", "hashing": "Hashwertberechnung", "image_ext_desc": "Durch Kommas getrennte Liste von Dateierweiterungen, die als Bilder identifiziert werden.", @@ -294,7 +294,7 @@ "entity_scrapers": "{entityType} Scraper", "excluded_tag_patterns_desc": "Reguläre Audrücke von Tags zum Ausschließen von Scraping Ergebnissen", "excluded_tag_patterns_head": "Tag Muster ausschließen", - "scraper": "Datenaggregator", + "scraper": "Scraper", "scrapers": "Scraper", "search_by_name": "Suche nach Name", "supported_types": "Unterstützte Typen", @@ -513,8 +513,8 @@ "movies": "{count, plural, one {Film} other {Filme}}", "performers": "{count, plural, one {Darsteller} other {Darsteller}}", "scenes": "{count, plural, one {Szene} other {Szenen}}", - "studios": "{Anzahl, Plural, ein {Studio} andere {Studios}}", - "tags": "{Anzahl, Plural, ein {Tag} andere {Tags}}" + "studios": "{count, plural, one {Studio} other {Studios}}", + "tags": "{count, plural, one {Tag} other {Tags}}" }, "country": "Land", "cover_image": "Titelbild", @@ -594,12 +594,12 @@ "image_previews": "Animierte Bildvorschauen", "image_previews_tooltip": "Animierte WebP-Vorschaubilder, nur erforderlich, wenn der Vorschautyp auf Animiertes Bild eingestellt ist.", "interactive_heatmap_speed": "Erzeugen von Heatmaps und Geschwindigkeiten für interaktive Szenen", - "marker_image_previews": "Animierte Markierungsvorschauen", - "marker_image_previews_tooltip": "Animierte Markierungs-WebP-Vorschauen, nur erforderlich, wenn der Vorschautyp auf Animiertes Bild eingestellt ist.", - "marker_screenshots": "Markierungsbild", - "marker_screenshots_tooltip": "Markierung statischer JPG-Bilder, nur erforderlich, wenn der Vorschautyp auf Statisches Bild eingestellt ist.", - "markers": "Markierungsvorschau", - "markers_tooltip": "20-Sekunden-Videos, die mit dem angegebenen Timecode beginnen.", + "marker_image_previews": "Animierte Vorschau für Markierungen", + "marker_image_previews_tooltip": "Animierte WebP-Vorschau für Markierungen, nur erforderlich, wenn der Vorschautyp auf Animiertes Bild eingestellt ist.", + "marker_screenshots": "Screenshots für Markierungen", + "marker_screenshots_tooltip": "Statische JPG-Bilder für Markierungen, nur erforderlich, wenn der Vorschautyp auf Statisches Bild eingestellt ist.", + "markers": "Vorschau für Markierungen", + "markers_tooltip": "20-Sekunden-Videos, die zum angegebenen Zeitpunkt beginnen.", "overwrite": "Vorhandene generierte Dateien überschreiben", "phash": "Perzeptuelle Hashes (zur Deduplizierung)", "preview_exclude_end_time_desc": "Schließen Sie die letzten x Sekunden von der Szenenvorschau aus. Dies kann ein Wert in Sekunden oder ein Prozentsatz (zB 2%) der gesamten Szenendauer sein.", @@ -634,14 +634,14 @@ "display_mode": { "grid": "Gitter", "list": "Liste", - "tagger": "Etikettierer", + "tagger": "Tagger", "unknown": "Unbekannt", "wall": "Wand" }, "donate": "Spenden", "dupe_check": { "description": "Bei Levels unterhalb von 'Exact' kann die Berechnung länger dauern. Bei niedrigeren Genauigkeitsstufen können auch falsch positive Ergebnisse zurückgegeben werden.", - "found_sets": "{setCount, plural, one{# Satz von Duplikaten gefunden.} andere {# Sätze von Duplikaten gefunden.}}", + "found_sets": "{setCount, plural, one{# Satz von Duplikaten gefunden.} other {# Sätze von Duplikaten gefunden.}}", "options": { "exact": "Genau", "high": "Hoch", @@ -653,9 +653,9 @@ }, "duration": "Dauer", "effect_filters": { - "aspect": "Aspekt", + "aspect": "Seitenverhältnis", "blue": "Blau", - "blur": "Verwischen", + "blur": "Unschärfe", "brightness": "Helligkeit", "contrast": "Kontrast", "gamma": "Gamma", @@ -675,7 +675,7 @@ }, "ethnicity": "Ethnizität", "eye_color": "Augenfarbe", - "fake_tits": "Gemachte Brüste", + "fake_tits": "Brustvergrößerungen", "false": "Falsch", "favourite": "Favorit", "file": "Datei", @@ -692,6 +692,14 @@ "gallery": "Galerie", "gallery_count": "Galerienanzahl", "gender": "Geschlecht", + "gender_types": { + "FEMALE": "Weiblich", + "INTERSEX": "Intersexuell", + "MALE": "Männlich", + "NON_BINARY": "Nicht-Binär", + "TRANSGENDER_FEMALE": "Trans* weiblich", + "TRANSGENDER_MALE": "Trans* männlich" + }, "hair_color": "Haarfarbe", "hasMarkers": "Hat Markierungen", "height": "Größe", @@ -700,17 +708,17 @@ "image_count": "Bilderanzahl", "images": "Bilder", "include_parent_tags": "Übergeordnete Tags einbeziehen", - "include_sub_studios": "Tochterstudios einbeziehen", - "include_sub_tags": "Sub-Tags einbeziehen", + "include_sub_studios": "Untergeordnete Studios einbeziehen", + "include_sub_tags": "Untergeordnete Tags einbeziehen", "instagram": "Instagram", "interactive": "Interaktiv", "interactive_speed": "Interaktive Geschwindigkeit", - "isMissing": "Wird vermisst", + "isMissing": "Fehlt", "library": "Bibliothek", "loading": { "generic": "Wird geladen…" }, - "marker_count": "Markierungsanzahl", + "marker_count": "Anzahl an Markierungen", "markers": "Markierungen", "measurements": "Maße", "media_info": { @@ -741,12 +749,12 @@ "pagination": { "first": "Erste", "last": "Letzte", - "next": "Nächster", - "previous": "Vorheriger" + "next": "Nächste", + "previous": "Vorherige" }, "parent_of": "Übergeordnet von {children}", - "parent_studios": "Elternstudios", - "parent_tag_count": "Übergeordnete Tag-Anzahl", + "parent_studios": "Übergeordnete Studios", + "parent_tag_count": "Anzahl übergeordneter Tags", "parent_tags": "Übergeordnete Tags", "part_of": "Übergeordnet von {parent}", "path": "Pfad", @@ -853,18 +861,18 @@ "stash_id": "Stash-ID", "stash_ids": "Stash IDs", "stats": { - "image_size": "Bildgröße", + "image_size": "Bildspeicher", "scenes_duration": "Szenendauer", - "scenes_size": "Szenengröße" + "scenes_size": "Szenenspeicher" }, "status": "Status: {statusText}", "studio": "Studio", "studio_depth": "Ebenen (leer für alle)", "studios": "Studios", - "sub_tag_count": "Anzahl an Sub-Tags", + "sub_tag_count": "Anzahl an untergeordneten Tags", "sub_tag_of": "Sub-Tag von {parent}", - "sub_tags": "Unterkategorien", - "subsidiary_studios": "Tochterstudios", + "sub_tags": "Untergeordnete Tags", + "subsidiary_studios": "Untergeordnete Studios", "synopsis": "Zusammenfassung", "tag": "Tag", "tag_count": "Tag-Anzahl", diff --git a/ui/v2.5/src/locales/es-ES.json b/ui/v2.5/src/locales/es-ES.json index 952052b7d..0269f7e6f 100644 --- a/ui/v2.5/src/locales/es-ES.json +++ b/ui/v2.5/src/locales/es-ES.json @@ -324,19 +324,22 @@ "backup_and_download": "Lleva a cabo una copia de seguridad de la base de datos y la guarda en un fichero de respaldo.", "backup_database": "Lleva a cabo una copia de seguridad de la base de datos en el mismo directorio en que se encuentre ésta. El formato de nombre del fichero generado es {filename_format}", "cleanup_desc": "Buscar ficheros eliminados del sistema de archivos y eliminarlos de la base de datos. PRECAUCIÓN: esta es una acción destructiva.", + "data_management": "Gestión de datos", "defaults_set": "Las opciones por defecto se han guardado y serán usadas cada vez que pulses el botoón de {action} en la página de Tareas.", "dont_include_file_extension_as_part_of_the_title": "No incluir la extensión del archivo como parte del título", + "empty_queue": "Actualmente no hay tareas en ejecución.", "export_to_json": "Exporta el contenido de la base de datos en formato JSON en el directorio de metadatos.", "generate": { "generating_from_paths": "Generando multimedia de soporte para las escenas en las siguientes rutas", "generating_scenes": "Generando multimedia de soporte para {num} {scene}" }, "generate_desc": "Generar imagen de soporte, conjuntos de imágenes, vídeo, vtt y resto de archivos.", - "generate_phashes_during_scan": "Generar hashes de percepción (Phashes) de los vídeos durante el escaneo (este valor es empleado para la búsqueda de archivos de vídeo duplicados y para la identificación de escenas)", + "generate_phashes_during_scan": "Generar hashes de percepción (Phashes)", + "generate_phashes_during_scan_tooltip": "Para búsqueda de duplicados e identificación de escenas.", "generate_previews_during_scan": "Generar imágenes previas durante el escaneo (vistas previas WebP animadas). Solo requerido si el tipo de previsualización seleccionado es “Imágenes animadas”)", "generate_sprites_during_scan": "Generar conjunto de imágenes o “sprites” durante el escaneo (para el depurador/limpiador de escenas)", - "generate_thumbnails_during_scan": "Generar miniaturas de las imágenes durante el escaneo.", - "generate_video_previews_during_scan": "Generar vistas previas durante el escaneo (vistas previas en vídeo que se muestran al pasar el enlace por encima de una escena)", + "generate_thumbnails_during_scan": "Generar miniaturas de las imágenes", + "generate_video_previews_during_scan": "Generar vistas previas", "generated_content": "Contenido generado", "identify": { "and_create_missing": "y crear no existentes", @@ -371,7 +374,7 @@ "scanning_paths": "Escaneando las siguientes rutas" }, "scan_for_content_desc": "Buscar contenido nuevo y añadirlo a la base de datos.", - "set_name_date_details_from_metadata_if_present": "Establecer el nombre, fecha y detalles desde los metadatos embebidos en el archivo (si se encuentran)" + "set_name_date_details_from_metadata_if_present": "Establecer el nombre, fecha y detalles desde los metadatos embebidos en el archivo" }, "tools": { "scene_duplicate_checker": "Comprobación de escenas duplicadas", @@ -393,6 +396,7 @@ "scene_tools": "Herramientas de escenas" }, "ui": { + "basic_settings": "Ajustes básicos", "custom_css": { "description": "La página debe ser recargada para que se lleven a cabo los cambios realizados.", "heading": "CSS personalizado", @@ -427,6 +431,7 @@ "heading": "Clave para conexión práctica" }, "images": { + "heading": "Imágenes", "options": { "write_image_thumbnails": { "description": "Guardar miniaturas de imagen en el sistema de archivos cuando son generadas al vuelo", @@ -672,7 +677,6 @@ "galleries": "Galerías", "gallery": "Galería", "gallery_count": "Número de galerías", - "gender": "Género", "hair_color": "Color de pelo", "hasMarkers": "Tiene marcadores", "height": "Estatura", diff --git a/ui/v2.5/src/locales/fi-FI.json b/ui/v2.5/src/locales/fi-FI.json index 628dc8859..51c630109 100644 --- a/ui/v2.5/src/locales/fi-FI.json +++ b/ui/v2.5/src/locales/fi-FI.json @@ -80,6 +80,7 @@ "select_folders": "Valitse kansiot", "select_none": "Peruuta Valinta", "selective_auto_tag": "Valikoiva automaattinen tunnisteiden asetus", + "selective_clean": "Valikoiva Puhdistus", "selective_scan": "Valikoiva skannaus", "set_as_default": "Aseta oletukseksi", "set_back_image": "Takakansi…", @@ -159,20 +160,28 @@ "build_hash": "Version tiiviste:", "build_time": "Version päiväys:", "check_for_new_version": "Tarkista onko uutta versiota saatavilla", + "latest_version": "Viimeisin Versio", "latest_version_build_hash": "Uusimman version tiiviste:", "new_version_notice": "[UUSI]", "stash_discord": "Liity {url} kanavallemme", - "stash_home": "Stashin kotisivut {ur}", + "stash_home": "Stashin kotisivut {url}", "stash_open_collective": "Tue meitä {url}", "stash_wiki": "Stashin {url} -sivu", "version": "Versio" }, + "application_paths": { + "heading": "Sovellukset Polut" + }, "categories": { "about": "Tietoja", "interface": "Käyttöliittymä", "logs": "Lokit", + "metadata_providers": "Metadatan tarjoajat", "plugins": "Lisäosat", "scraping": "Kaavinta", + "security": "Turvallisuus", + "services": "Palvelut", + "system": "Järjestelmä", "tasks": "Tehtävät", "tools": "Työkalut" }, @@ -195,6 +204,10 @@ "api_key_desc": "API -avain ulkoisille järjestelmille. Tarvitaan vain jos käyttäjätunnus ja salasana on määritelty. Käyttäjätunnus taytyy tallentaa ennen API -avaimen luomista.", "authentication": "Autentikointi", "clear_api_key": "Puhdista API -avain", + "credentials": { + "description": "Tunnukset, joiden pääsyä stashiin rajoitetaan.", + "heading": "Tunnukset" + }, "generate_api_key": "Luo API -avain", "log_file": "Lokitiedosto", "log_file_desc": "Polku lokitiedostoon. Jos jätetty tyhjäksi, ei lokitiedostoa tehdä. Vaatii uudelleenkäynnistyksen.", @@ -202,7 +215,7 @@ "log_http_desc": "Kirjaa http -pyynnöt komentoriville. Vaatii uudelleenkäynnistyksen.", "log_to_terminal": "Kirjoita loki komentoriville", "log_to_terminal_desc": "Kirjaa lokin komentoriville lokitiedoston sijaan. Aina päällä jos lokitiedosto ei ole käytössä. Vaatii uudelleenkäynnistyksen.", - "maximum_session_age": "Session keston yläraja", + "maximum_session_age": "Istunnon keston yläraja", "maximum_session_age_desc": "Yläraja toimettomalle ajalle sekunneissa ennen kuin kirjautuminen vanhenee.", "password": "Salasana", "password_desc": "Salasana Stashiin. Jätä tyhjäksi, mikäli et halua autentikointia", @@ -233,7 +246,7 @@ "generated_file_naming_hash_desc": "Käytä MD5:ttä tai oshashia tiedostojen nimien generoimiseen. Tämän muuttaminen vaatii, että kaikilla kohtauksilla on MD5 tai oshash. Tämän valinnan muuttamisen jälkeen kaikki generoidut tukitiedostot täytyy joko migraatioida tai generoida uudelleen. Katso lisää migraatiosta tehtävät -sivulta.", "generated_file_naming_hash_head": "Generoitu tiiviste tiedoston nimeämistä varten", "generated_files_location": "Kansion polku generoiduille tiedostoille (kohtauksen merkit, kohtauksien esikatselut, tms)", - "generated_path_head": "Generoitu polku", + "generated_path_head": "Generoitujen tiedostojen polku", "hashing": "Tiivisteen luominen", "image_ext_desc": "Pilkuilla erotettu lista tiedostopäätteistä, jotka tulkitaan kuviksi.", "image_ext_head": "Kuvien tiedostopäätteet", @@ -254,12 +267,21 @@ "preview_generation": "Esikatsele generointi", "scraper_user_agent": "Kaapijan käyttäjäagentti", "scraper_user_agent_desc": "Käyttäjäagenttikenttä, jota kaavittaessa käytetään http pyynnöissä", + "scrapers_path": { + "description": "Kaapijan konfiguraatiotiedostojen kansion sijainti", + "heading": "Kaapijoiden polku" + }, "scraping": "Kaapiminen", "sqlite_location": "SQLite -tietokannan tiedostosijainti (vaatii uudelleenkäynnistyksen)", "video_ext_desc": "Pilkuilla erotettu lista tiedostopäätteistä, jotka tulkitaan videoiksi.", "video_ext_head": "Videoiden päätteet", "video_head": "Video" }, + "library": { + "exclusions": "Poisjättäminen", + "gallery_and_image_options": "Galleria- ja kuva-asetukset", + "media_content_extensions": "Mediasisällön tiedostopäätteet" + }, "logs": { "log_level": "Lokin taso" }, @@ -283,6 +305,9 @@ "name": "Nimi", "title": "Stash-box päätepisteet" }, + "system": { + "transcoding": "Transkoodaus" + }, "tasks": { "added_job_to_queue": "Lisättiin {operation_name} tehtäväjonoon", "auto_tag": { @@ -298,16 +323,20 @@ "data_management": "Datan hallinta", "defaults_set": "Oletukset on asetettu ja niitä käytetään kun painat {action} -painiketta Tehtävät -sivulla.", "dont_include_file_extension_as_part_of_the_title": "Elä sisällytä tiedostopäätettä tiedoston nimeen", + "empty_queue": "Tehtäviä ei ole menossa.", "export_to_json": "Vie tietokannan sisällön JSON -formaatissa samaan kansioon kuin missä tietokanta on.", "generate": { "generating_from_paths": "Generoidaan kohtauksille seuraavista poluista", "generating_scenes": "Generoidaan {num} {scene}" }, "generate_desc": "Generoi kuvat, esikatselut ja muut tukitiedostot.", - "generate_phashes_during_scan": "Generoi phash -tiiviste skannauksen aikana (kaksoiskappaleiden ja kohtauksen tunnistamiseen)", - "generate_previews_during_scan": "Generoi esikatselukuvat skannauksen aikana (animoitu WebP esikatselu, vaaditaan vain jos esikatselun tyypiksi on valittu animoitu kuva)", - "generate_thumbnails_during_scan": "Generoi esikatselukuva skannauksen aikana.", - "generate_video_previews_during_scan": "Generoi esikatselu skannauksen aikana (videoesikatselu, joka näytetään kun osoitin menee kohtauksen päälle)", + "generate_phashes_during_scan": "Generoi phash -tiivisteet", + "generate_phashes_during_scan_tooltip": "Kaksoiskappaleiden löytämiseen ja kohtauksien tunnistamiseen.", + "generate_previews_during_scan": "Generoi animoidut esikatselukuvat", + "generate_previews_during_scan_tooltip": "Generoi animoidun WebP -esikatselun, vaaditaan vain jos esikatselun tyypiksi on valittu animoitu kuva.", + "generate_thumbnails_during_scan": "Generoi esikatselukuva skannauksen aikana", + "generate_video_previews_during_scan": "Generoi esikatselu", + "generate_video_previews_during_scan_tooltip": "Generoi videoesikatselun, joka näytetään kun osoitin menee kohtauksen päälle", "generated_content": "Generoitu sisältö", "identify": { "and_create_missing": "ja luo puuttuvat", @@ -342,7 +371,7 @@ "scanning_paths": "Skannataan seuraavia polkuja" }, "scan_for_content_desc": "Skannaa uusi sisältö ja lisää se tietokantaan.", - "set_name_date_details_from_metadata_if_present": "Aseta nimi, päivä ja tiedot metadatasta (jos saatavilla)" + "set_name_date_details_from_metadata_if_present": "Aseta nimi, päivä ja tiedot metadatasta" }, "tools": { "scene_duplicate_checker": "Kohtauksien kaksoiskappaleiden tarkistus", @@ -359,8 +388,9 @@ "scene_tools": "Kohtauksen työkalut" }, "ui": { + "basic_settings": "Perusasetukset", "custom_css": { - "description": "Sivu täytyy ladata uudellee, jotta muutokset tulevat voimaan.", + "description": "Sivu täytyy ladata uudelleen, jotta muutokset tulevat voimaan.", "heading": "Mukautettu CSS", "option_label": "Mukautettu CSS käytössä" }, @@ -388,6 +418,7 @@ "description": "Viive millisekunneissa interaktiivisille skripteille kun toistetaan." }, "images": { + "heading": "Kuvat", "options": { "write_image_thumbnails": { "description": "Kirjoita kuvien esikatselut levylle kun generoidaan", @@ -509,6 +540,7 @@ "delete_object_overflow": "…ja {count} muuta {count, plural, one {{singularEntity}} other {{pluralEntity}}}.", "delete_object_title": "Poista {count, plural, one {{singularEntity}} other {{pluralEntity}}}", "edit_entity_title": "Muokkaa {count, plural, one {{singularEntity}} other {{pluralEntity}}}", + "export_include_related_objects": "Sisällytä liittyvät objektit vientiin", "export_title": "Vie", "lightbox": { "delay": "Viive (sek)", @@ -535,20 +567,29 @@ }, "overwrite_filter_confirm": "Haluatko varmasti ylikirjoittaa jo olemassaolevan {entityName}?", "scene_gen": { - "image_previews": "Esikatselukuvat (Animoitu WebP esikatselu, vaadittu vain jos esikatselutyypiksi on asetettu animoitu kuva)", - "marker_image_previews": "Merkkien esikatselukuvat (Animoitu WebP esikatselu, vaadittu vain jos esikatselutyypiksi on asetettu animoitu kuva)", - "marker_screenshots": "Merkkien esikatselukuvat (staattinen JPG -kuva, vaadittu vain jos esikatselutyypiksi on asetettu staattinen kuva)", - "markers": "Merkit (20 sekunnin video jokaisen aikakoodin alusta)", + "force_transcodes": "Pakota transkoodaus", + "force_transcodes_tooltip": "Oletuksena transkoodaus tehdään vain, mikäli selain ei tue videotiedostoa. Jos tämä valinta on päällä, transkoodaus tehdään vaikka selain näyttäisi tukevan videotiedostoa.", + "image_previews": "Animoidut esikatselukuvat", + "image_previews_tooltip": "Animoidut WebP esikatselut, vaaditaan vain jos esikatselun tyyppi on Animoitu kuva.", + "marker_image_previews": "Animoidut merkkien esikatselukuvat", + "marker_image_previews_tooltip": "Animoidut merkkien WebP esikatselut, vaaditaan vain jos esikatselun tyypiksi on valittu Animoitu kuva.", + "marker_screenshots": "Merkkien esikatselukuvat", + "marker_screenshots_tooltip": "Merkkien staattinen JPG -kuva, vaadittu vain jos esikatselutyypiksi on asetettu staattinen kuva.", + "markers": "Merkkien esikatselut", + "markers_tooltip": "20 sekunnin video jokaisen aikakoodin alusta.", "overwrite": "Ylikirjoita olemassaolevat generoidut tiedostot", "phash": "Tiiviste (kaksoiskappaileiden tunnistamiseen)", "preview_exclude_end_time_desc": "Jätä viimeiset x sekuntia pois esikatseluista. Arvo voi olla sekunneissa tai prosenteissa (esim. 2%) kohtauksen kestosta.", "preview_exclude_end_time_head": "Lopusta poisjätettävä aika", "preview_exclude_start_time_desc": "Jätä ensimmäiset x sekuntia pois esikatseluista. Arvo voi olla sekunneissa tai prosenteissa (esim. 2%) kohtauksen kestosta.", "preview_exclude_start_time_head": "Alusta poisjätettävä aika", + "preview_generation_options": "Esikatselun generoinnin asetukset", "preview_options": "Esikatseluasetukset", "preview_preset_head": "Esikatseluidun enkoodaus", - "transcodes": "Transkoodaus (MP4 muunto niille tiedostomuodoille, joita ei tueta)", - "video_previews": "Esikatselu (esikatseluviedo, joka näytetään kun hiiri on kohtauksen päällä)" + "transcodes": "Transkoodaus", + "transcodes_tooltip": "MP4 muunto niille tiedostomuodoille, joita ei tueta", + "video_previews": "Esikatselu", + "video_previews_tooltip": "Esikatseluviedo, joka näytetään kun hiiri on kohtauksen päällä" }, "scenes_found": "{count} kohtausta löydetty", "scrape_entity_query": "{entity_type}kaavintajono", @@ -620,7 +661,9 @@ "galleries": "Galleriat", "gallery": "Galleria", "gallery_count": "Gallerioiden määrä", - "gender": "Sukupuoli", + "gender": { + "NON_BINARY": "Ei-Binäärinen" + }, "hair_color": "Hiusten väri", "hasMarkers": "On merkki", "height": "Pituus", @@ -633,6 +676,7 @@ "include_sub_tags": "Sisällytä alitunnisteet", "instagram": "Instagram", "interactive": "Interaktiivinen", + "interactive_speed": "Interaktiivinen nopeus", "isMissing": "Puuttuu", "library": "Kirjasto", "loading": { @@ -646,6 +690,7 @@ "checksum": "Tarkistussumma", "downloaded_from": "Ladattu kohteesta", "hash": "Tiiviste", + "interactive_speed": "Interaktiivinen nopeus", "performer_card": { "age": "{age} {years_old}", "age_context": "{age} {years_old} tässä kohtauksessa" @@ -730,7 +775,10 @@ "migration_failed": "Migraatio ei onnistunut", "migration_failed_error": "Seuraava virhe tuli tietokannan migraatiossa:", "migration_failed_help": "Tee tarvittavat korjaukset. Muussa tapauksessa tee ilmoitus bugista {githubLink} tai pyydä apua {discordLink}.", - "migration_required": "Migraatio vaaditaan" + "migration_irreversible_warning": "Migraatio ei ole peruutettavissa. Kun migraatio on suoritettu, tietokanta ei ole enää yhteensopiva stashin vanhempien versioiden kanssa.", + "migration_required": "Migraatio vaaditaan", + "perform_schema_migration": "Suorita migraatio", + "schema_too_old": "Tämänhetkinen stash -tietokannan muodon versio on {databaseSchema} ja se pitää muuttaa versioon {appSchema}. Tämä versio Stashista ei toimi ilman tietokannan migraatiota." }, "paths": { "database_filename_empty_for_default": "tietokannan tiedostonimi (oletus jos tyhjä)", @@ -754,6 +802,7 @@ "your_system_has_been_created": "Kaikki hyvin! Järjestelmä on nyt luotu!" }, "welcome": { + "in_current_stash_directory": "HOME/.stash kansioon", "store_stash_config": "Minne haluat tallentaa Stash -konfiguraation?", "unable_to_locate_config": "Mikäli luet tätä, Stash ei löydä olemassaolevaa konfiguraatiota. Tämä velho auttaa sinua uuden konfiguraation luomisessa." }, @@ -807,6 +856,7 @@ "up-dir": "Ylös", "updated_at": "Päivitetty", "url": "URL", + "videos": "Videot", "weight": "Paino", "years_old": "-vuotias" } diff --git a/ui/v2.5/src/locales/fr-FR.json b/ui/v2.5/src/locales/fr-FR.json index 7a8a01fda..52dd33695 100644 --- a/ui/v2.5/src/locales/fr-FR.json +++ b/ui/v2.5/src/locales/fr-FR.json @@ -80,6 +80,7 @@ "select_folders": "Sélectionner des répertoires", "select_none": "Ne rien sélectionner", "selective_auto_tag": "Taggage automatique de la sélection", + "selective_clean": "Nettoyage sélectif", "selective_scan": "Scan sélectif", "set_as_default": "Définir comme valeur par défaut", "set_back_image": "Image Verso…", @@ -266,6 +267,9 @@ "preview_generation": "Générer les aperçus", "scraper_user_agent": "User-Agent pour les Scraper", "scraper_user_agent_desc": "Chaîne User-Agent utilisée dans les requêtes http lors du Scraping.", + "scrapers_path": { + "heading": "Chemin des scrapers" + }, "scraping": "Scraping", "sqlite_location": "Emplacement du fichier de base de données SQLite (nécéssite un redémarrage)", "video_ext_desc": "Liste des extensions de fichiers Vidéos.", @@ -583,9 +587,13 @@ }, "overwrite_filter_confirm": "Êtes-vous sûr de vouloir écraser le filtre sauvegardé {entityName} ?", "scene_gen": { + "force_transcodes": "Forcer la génération de transcodage", + "force_transcodes_tooltip": "Par défaut, les transcodages ne sont générés que lorsque le fichier vidéo n'est pas pris en charge dans le navigateur. Lorsqu'il est activé, les transcodes seront générés même lorsque le fichier vidéo semble être pris en charge dans le navigateur.", "image_previews": "Aperçus image (Image WebP animée. Requis seulement si le type d'Aperçu est défini sur Image Animée)", + "image_previews_tooltip": "Aperçus animés (en WebP), requis uniquement si le type d'aperçu est défini sur Image animée.", "interactive_heatmap_speed": "Générer des cartes thermiques et des vitesses pour des scènes interactives", "marker_image_previews": "Aperçus Marqueurs (Image WebP animée. Requis seulement si le type d'aperçu est défini sur Image Animée)", + "marker_image_previews_tooltip": "Aperçus animé des marqueurs (en WebP), requis uniquement si le type d'aperçu est défini sur Image animée.", "marker_screenshots": "Capture d'écran Marqueurs (Image JPG fixe. Requis seulement si le type d'Aperçu est défini sur Image Fixe)", "markers": "Aperçus des Marqueurs", "markers_tooltip": "Vidéo de 20 secondes qui commence au timecode indiqué.", @@ -595,6 +603,7 @@ "preview_exclude_end_time_head": "Exclure à la fin", "preview_exclude_start_time_desc": "Exclure les x premières secondes de la vidéo pour la génération de l'aperçu. La valeur peut-être exprimée en secondes ou bien en pourcentage (ex : 2%) de la durée totale de la vidéo.", "preview_exclude_start_time_head": "Exclure au début", + "preview_generation_options": "Options des générations d'aperçu", "preview_options": "Options d'Aperçus", "preview_preset_desc": "Le Preset d'encodage régule la taille, la qualité et le temps d'encodage des aperçus. Les Preset plus bas que “slow” n'apportent pas de gain significatif et ne sont pas recommandés.", "preview_preset_head": "Preset d'encodage de l'aperçu", @@ -680,6 +689,14 @@ "gallery": "Galerie", "gallery_count": "Nombre de Galeries", "gender": "Genre", + "gender_types": { + "FEMALE": "Femme", + "INTERSEX": "Intersexe", + "MALE": "Homme", + "NON_BINARY": "Non Binaire", + "TRANSGENDER_FEMALE": "Femme transgenre", + "TRANSGENDER_MALE": "Homme transgenre" + }, "hair_color": "Couleur des cheveux", "hasMarkers": "Possède des marqueurs", "height": "Taille", diff --git a/ui/v2.5/src/locales/it-IT.json b/ui/v2.5/src/locales/it-IT.json index 1265eb831..2e85ff4b6 100644 --- a/ui/v2.5/src/locales/it-IT.json +++ b/ui/v2.5/src/locales/it-IT.json @@ -692,6 +692,14 @@ "gallery": "Galleria", "gallery_count": "Numero Gallerie", "gender": "Genere", + "gender_types": { + "FEMALE": "Donna", + "INTERSEX": "Intersessualità", + "MALE": "Uomo", + "NON_BINARY": "Non-Binario", + "TRANSGENDER_FEMALE": "Donna Transgender", + "TRANSGENDER_MALE": "Uomo Transgender" + }, "hair_color": "Colore Capelli", "hasMarkers": "Ha Marcatori", "height": "Altezza", diff --git a/ui/v2.5/src/locales/nl-NL.json b/ui/v2.5/src/locales/nl-NL.json index 66d18abfb..06e7f85bf 100644 --- a/ui/v2.5/src/locales/nl-NL.json +++ b/ui/v2.5/src/locales/nl-NL.json @@ -80,6 +80,7 @@ "select_folders": "Selecteer bestandsmappen", "select_none": "Selecteer Niets", "selective_auto_tag": "Selectieve automatische Tag", + "selective_clean": "Selectief Opkuisen", "selective_scan": "Selectief Aftasten", "set_as_default": "Stel als standaard in", "set_back_image": "Achtergrond afbeelding…", @@ -159,6 +160,7 @@ "build_hash": "Bouw hash:", "build_time": "Bouwtijd:", "check_for_new_version": "Controleren op nieuwe versie", + "latest_version": "Laatste versie", "latest_version_build_hash": "Nieuwste versie Bouw Hash:", "new_version_notice": "[NIEUW]", "stash_discord": "Word lid van ons {url} kanaal", @@ -167,12 +169,19 @@ "stash_wiki": "Stash {url} pagina", "version": "Versie" }, + "application_paths": { + "heading": "aplicatie pad" + }, "categories": { "about": "Over", "interface": "Interface", "logs": "Logboeken", + "metadata_providers": "Metadata voorzieners", "plugins": "Plugins", "scraping": "Schraper", + "security": "Beveiliging", + "services": "Diensten", + "system": "Systeem", "tasks": "Taken", "tools": "Gereedschap" }, @@ -195,6 +204,10 @@ "api_key_desc": "API sleutel voor externe systemen. Alleen vereist wanneer gebruikersnaam/wachtwoord is geconfigureerd. Gebruikersnaam moet worden opgeslagen voordat API sleutel wordt gegenereerd.", "authentication": "Authenticatie", "clear_api_key": "Duidelijke API sleutel", + "credentials": { + "description": "Credentials om toegang to stash te beperken.", + "heading": "Inloggegevens" + }, "generate_api_key": "Maak een API-sleutel", "log_file": "Logboek", "log_file_desc": "Pad naar het bestand waar naar te loggen. Laat leeg om niet logboek niet naar bestand te schrijven. Herstarten nodig.", @@ -254,12 +267,21 @@ "preview_generation": "Voorbeeld genereren", "scraper_user_agent": "User Agent van de schraper", "scraper_user_agent_desc": "User-agent gebruikt tijdens het schrapen van HTTP verzoeken", + "scrapers_path": { + "description": "Map locatie van schraper configuratie bestanden", + "heading": "Schrapers Pad" + }, "scraping": "Schrapen", "sqlite_location": "Bestandspad voor de SQLite database (vereist een herstart)", "video_ext_desc": "Komma gescheiden lijst van bestandsextensie die worden aangemerkt als video.", "video_ext_head": "Video extensies", "video_head": "Video" }, + "library": { + "exclusions": "Uitzonderingen", + "gallery_and_image_options": "Gallerij en Foto opties", + "media_content_extensions": "Media content extensies" + }, "logs": { "log_level": "Log niveau" }, @@ -287,6 +309,9 @@ "name": "Naam", "title": "Stash-box Endpoints" }, + "system": { + "transcoding": "Transcoderen" + }, "tasks": { "added_job_to_queue": "{operation_name} aan takenrij toegevoegd", "auto_tag": { @@ -300,22 +325,583 @@ "backup_database": "Voert een backup uit naar hetzelfde pad als de database, met het bestandsformaat {filename_format}", "cleanup_desc": "Controleer op missende bestanden en verwijder deze uit de database. Dit is een destructieve handeling.", "data_management": "Opslagbeheer", + "defaults_set": "Standaarden zijn ingesteld en zullen gebruikt worden wanneer de {action} knop op de Taken pagina ingedrukt wordt.", + "dont_include_file_extension_as_part_of_the_title": "Neem geen bestandsextensie op als onderdeel van de titel", + "empty_queue": "Er zijn momenteel geen taken uitgevoerd.", + "export_to_json": "Exporteert de database-inhoud in JSON-formaat naar de metadata map.", + "generate": { + "generating_from_paths": "Genereren voor scènes uit de volgende paden", + "generating_scenes": "Genereren voor {num} {scene}" + }, + "generate_desc": "Genereer ondersteunend foto, sprite, video, vtt en andere bestanden.", + "generate_phashes_during_scan": "Genereer perceptuele hashes", + "generate_phashes_during_scan_tooltip": "Voor deduplicatie en scène-identificatie.", + "generate_previews_during_scan": "Genereer geanimeerde afbeelding previews", + "generate_previews_during_scan_tooltip": "Genereer geanimeerde webp-previews, alleen vereist als het voorbeeldtype is ingesteld op geanimeerde afbeelding.", + "generate_sprites_during_scan": "Genereer scrubber sprites", + "generate_thumbnails_during_scan": "Genereer miniaturen voor afbeeldingen", + "generate_video_previews_during_scan": "Previews genereren", + "generate_video_previews_during_scan_tooltip": "Genereer video-previews die afspelen bij het zweven over een scène", + "generated_content": "Gegenereerde inhoud", "identify": { + "and_create_missing": "en creëer missende", "create_missing": "Missende aanmaken", - "default_options": "Standaard instellingen" - } + "default_options": "Standaard instellingen", + "description": "Stel automatisch scène metadata in met behulp van stash-box en schraperbronnen.", + "explicit_set_description": "De volgende opties worden gebruikt waar niet wordt opgeheven in de bronspecifieke opties.", + "field": "Veld", + "field_behaviour": "{strategy} {field}", + "field_options": "Veld Opties", + "heading": "Identificeer", + "identifying_from_paths": "Scènes identificeren uit de volgende paden", + "identifying_scenes": "Identificeren {num} {scene}", + "include_male_performers": "Inclusief mannelijke artiesten", + "set_cover_images": "Set Cover-afbeeldingen", + "set_organized": "Georganiseerde vlag instellen", + "source": "Bron", + "source_options": "{source} Opties", + "sources": "Bronnen", + "strategy": "Strategie" + }, + "import_from_exported_json": "Import van geëxporteerde JSON in de map metadata. Maakt de bestaande database leeg.", + "incremental_import": "Incrementele import uit een meegeleverde export zip-bestand.", + "job_queue": "Taakwachtrij", + "maintenance": "Onderhoud", + "migrate_hash_files": "Gebruikt na het wijzigen van de gegenereerde bestandsnaaming Hash om bestaande gegenereerde bestanden te hernoemen naar het nieuwe hash-formaat.", + "migrations": "Migraties", + "only_dry_run": "Voer alleen een testronde uit. Verwijder niets", + "plugin_tasks": "Plugin Taken", + "scan": { + "scanning_all_paths": "Alle paden scannen", + "scanning_paths": "Scannen van de volgende paden" + }, + "scan_for_content_desc": "Scan naar nieuwe inhoud en voeg deze toe aan de database.", + "set_name_date_details_from_metadata_if_present": "Stel de naam, datum, details in vanuit Embedded File Metadata" + }, + "tools": { + "scene_duplicate_checker": "Scène Duplicator Checker", + "scene_filename_parser": { + "add_field": "Veld toevoegen", + "capitalize_title": "Kapitaliseer de titel", + "display_fields": "Weergavevelden", + "escape_chars": "Gebruik \\ om letterlijke karakters te ontsnappen", + "filename": "Bestandsnaam", + "filename_pattern": "Bestandsnaam patroon", + "ignore_organized": "Negeer georganiseerde scenes", + "ignored_words": "Genegeerde woorden", + "matches_with": "Komt overeen met {i}", + "select_parser_recipe": "Selecteer Parser Recept", + "title": "Scène-bestandsnaam parser", + "whitespace_chars": "WhiteSpace-tekens", + "whitespace_chars_desc": "Deze tekens worden vervangen door witruimte in de titel" + }, + "scene_tools": "Scene gereedschap" + }, + "ui": { + "basic_settings": "Basis instellingen", + "custom_css": { + "description": "Pagina moet worden herladen voordat wijzigingen van kracht worden.", + "heading": "Aangepaste CSS", + "option_label": "Aangepaste CSS ingeschakeld" + }, + "delete_options": { + "description": "Standaardinstellingen bij het verwijderen van afbeeldingen, galerijen en scènes.", + "heading": "Verwijder opties", + "options": { + "delete_file": "Verwijder het bestand standaard", + "delete_generated_supporting_files": "Verwijder gegenereerde ondersteunende bestanden standaard" + } + }, + "desktop_integration": { + "desktop_integration": "Desktop-integratie", + "skip_opening_browser": "Sla het openen van een browser over", + "skip_opening_browser_on_startup": "Sla het automatisch openen van een browser over tijdens het opstarten" + }, + "editing": { + "disable_dropdown_create": { + "description": "Verwijder de mogelijkheid om nieuwe objecten te maken uit de dropdown menu", + "heading": "Schakel het maken van dropdowns uit" + }, + "heading": "Aanpassen" + }, + "funscript_offset": { + "description": "Time Offset in milliseconden voor het afspelen van interactieve scripts.", + "heading": "Funscript Offset (ms)" + }, + "handy_connection_key": { + "description": "Handy connection key om te gebruiken voor interactieve scènes. Instellen van deze sleutel staat Stash toe om uw huidige scène-informatie met HandyFeeling.com te delen", + "heading": "Handy Connection Key" + }, + "images": { + "heading": "Afbeeldingen", + "options": { + "write_image_thumbnails": { + "description": "Schrijf de beeldminiaturen naar de schijf wanneer on-the-fly is gegenereerd", + "heading": "Schrijf beeldminiaturen" + } + } + }, + "interactive_options": "Interactieve Opties", + "language": { + "heading": "Taal" + }, + "max_loop_duration": { + "description": "Maximale scène-duur waarbij scènespeler de video loopt - 0 om uit te schakelen", + "heading": "Maximale lusduur" + }, + "menu_items": { + "description": "Toon of verberg verschillende soorten inhoud op de navigatiebalk", + "heading": "Menu Items" + }, + "performers": { + "options": { + "image_location": { + "description": "Aangepast pad voor standaard performers. Laat leeg om in gebouwde standaardinstellingen te gebruiken", + "heading": "Aangepaste Performer afbeelding pad" + } + } + }, + "preview_type": { + "description": "Configuratie voor wanditems", + "heading": "Voorbeeld Type", + "options": { + "animated": "Geanimeerde afbeelding", + "static": "Statische afbeelding", + "video": "Video" + } + }, + "scene_list": { + "heading": "Scene lijst", + "options": { + "show_studio_as_text": "Laat studio's als text zien" + } + }, + "scene_player": { + "heading": "Scènespeler", + "options": { + "auto_start_video": "Auto-start video", + "auto_start_video_on_play_selected": { + "description": "scene automatisch starten bij het afspelen van geselecteerde of willekeurige van scènes pagina", + "heading": "Auto-start video bij het afspelen van geselecteerde" + }, + "continue_playlist_default": { + "description": "Speel de volgende scène in de wachtrij wanneer video is voltooid", + "heading": "Ga Standaard door met de afspeellijst" + } + } + }, + "scene_wall": { + "heading": "Scène / markeermuur", + "options": { + "display_title": "Titel en tags weergeven", + "toggle_sound": "Geluid inschakelen" + } + }, + "slideshow_delay": { + "description": "Diavoorstelling is beschikbaar in galerijen in de muurweergavemodus", + "heading": "Diavoorstellingsvertraging" + }, + "title": "Gebruikers interface" } }, + "configuration": "Configuratie", + "countables": { + "files": "{count, plural, one {Bestand} other {Bestanden}}", + "galleries": "{count, plural, one {Galerij} other {Galerijen}}", + "images": "{count, plural, one {Afbeelding} other {Afbeeldingen}}", + "markers": "{count, plural, one {Marker} other {Markers}}", + "movies": "{count, plural, one {Film} other {Films}}", + "performers": "{count, plural, one {Performer} other {Performers}}", + "scenes": "{count, plural, one {Scene} other {Scenes}}", + "studios": "{count, plural, one {Studio} other {Studios}}", + "tags": "{count, plural, one {Tag} other {Tags}}" + }, + "country": "Land", + "cover_image": "Cover afbeelding", + "created_at": "Gemaakt op", + "criterion": { + "greater_than": "Groter dan", + "less_than": "Minder dan", + "value": "Waarde" + }, + "criterion_modifier": { + "between": "tussen", + "equals": "is", + "excludes": "uitsluitend", + "format_string": "{criterion} {modifierString} {valueString}", + "greater_than": "is groter dan", + "includes": "omvat", + "includes_all": "omvat alles", + "is_null": "is null", + "less_than": "is minder dan", + "matches_regex": "komt overeen met regex", + "not_between": "niet tussen", + "not_equals": "is geen", + "not_matches_regex": "komt niet overeen met regex", + "not_null": "is geen null" + }, + "custom": "Aangepast", + "date": "Datum", + "death_date": "Sterfdatum", + "death_year": "Sterfjaar", + "descending": "Aflopend", + "detail": "Detail", + "details": "Details", + "developmentVersion": "Ontwikkelingsversie", + "dialogs": { + "aliases_must_be_unique": "aliases moeten uniek zijn", + "delete_alert": "De volgende {count, plural, one {{singularEntity}} other {{pluralEntity}}} zal permanent verwijderd worden:", + "delete_confirm": "Weet je zeker dat je {entityName} wilt verwijderen?", + "delete_entity_desc": "{count, plural, one {Weet u zeker dat u deze {singularEntity} wilt verwijderen? Tenzij het bestand ook wordt verwijderd, wordt deze {singularEntity} opnieuw toegevoegd wanneer de scan wordt uitgevoerd.} other {Weet u zeker dat u deze {pluralEntity} wilt verwijderen? Tenzij de bestanden ook worden verwijderd, worden deze {pluralEntity} opnieuw toegevoegd wanneer de scan wordt uitgevoerd.}}", + "delete_entity_title": "{count, plural, one {Verwijder{singularEntity}} other {Verwijder{pluralEntity}}}", + "delete_galleries_extra": "... plus alle afbeeldingsbestanden die niet aan een andere galerij zijn gekoppeld.", + "delete_gallery_files": "Verwijder de galerijmap/zip-bestand en alle afbeeldingen die niet aan een andere galerij zijn gekoppeld.", + "delete_object_desc": "Weet u zeker dat u {count, plural, one {this {singularEntity}} other {these {pluralEntity}}} wilt gaan verwijderen?", + "delete_object_overflow": "…en {count} other {count, plural, one {{singularEntity}} other {{pluralEntity}}}.", + "delete_object_title": "Verwijder {count, plural, one {{singularEntity}} other {{pluralEntity}}}", + "edit_entity_title": "Wijzig {count, plural, one {{singularEntity}} other {{pluralEntity}}}", + "export_include_related_objects": "Verwante objecten opnemen met het exporteren", + "export_title": "Exporteer", + "lightbox": { + "delay": "Vertraging (Sec)", + "display_mode": { + "fit_horizontally": "Pas horizontaal", + "fit_to_screen": "Pas naar scherm", + "label": "Weergave modus", + "original": "Orgineel" + }, + "options": "Opties", + "reset_zoom_on_nav": "Zoomniveau resetten bij het wijzigen van afbeelding", + "scale_up": { + "description": "Schaal kleinere afbeeldingen omhoog om het scherm te vullen", + "label": "Opschalen om te passen" + }, + "scroll_mode": { + "description": "Houd shift ingedrukt om tijdelijk een andere modus te gebruiken.", + "label": "Scroll modus", + "pan_y": "Pan Y", + "zoom": "Zoom" + } + }, + "merge_tags": { + "destination": "Bestemming", + "source": "Afkomst" + }, + "overwrite_filter_confirm": "Weet u zeker dat u de bestaande opgeslagen zoekopdracht {entityName} wilt overschrijven?", + "scene_gen": { + "force_transcodes": "Genereren van transcode forceren", + "force_transcodes_tooltip": "Standaard worden transcodes alleen gegenereerd als het videobestand niet wordt ondersteund in de browser. Indien ingeschakeld, worden transcodes gegenereerd, zelfs als het videobestand in de browser lijkt te worden ondersteund.", + "image_previews": "Geanimeerde afbeelding voorbeelden", + "image_previews_tooltip": "Geanimeerde WebP-voorbeelden, alleen vereist als Voorbeeldtype is ingesteld op Geanimeerde afbeelding.", + "interactive_heatmap_speed": "Genereer heatmaps en snelheden voor interactieve scènes", + "marker_image_previews": "Voorvertoningen van geanimeerde markeringen", + "marker_image_previews_tooltip": "Geanimeerde markering WebP-voorbeelden, alleen vereist als Voorbeeldtype is ingesteld op Geanimeerde afbeelding.", + "marker_screenshots": "Markeer Screenshots", + "marker_screenshots_tooltip": "Markeer statische JPG-afbeeldingen, alleen vereist als Voorbeeldtype is ingesteld op Statische afbeelding.", + "markers": "Marker Voorbeelden", + "markers_tooltip": "Video's van 20 seconden die beginnen op de opgegeven tijdcode.", + "overwrite": "Bestaande gegenereerde bestanden overschrijven", + "phash": "Perceptuele hashes (voor deduplicatie)", + "preview_exclude_end_time_desc": "Sluit de laatste x seconden uit van scènevoorbeelden. Dit kan een waarde in seconden zijn, of een percentage (bijv. 2%) van de totale duur van de scène.", + "preview_exclude_end_time_head": "Eindtijd uitsluiten", + "preview_exclude_start_time_desc": "Sluit de eerste x seconden uit van scènevoorbeelden. Dit kan een waarde in seconden zijn, of een percentage (bijv. 2%) van de totale duur van de scène.", + "preview_exclude_start_time_head": "Starttijd uitsluiten", + "preview_generation_options": "Opties voor het genereren van voorbeelden", + "preview_options": "Voorbeeld opties", + "preview_preset_desc": "De voorinstelling regelt de grootte, kwaliteit en coderingstijd van het genereren van voorbeelden. Voorinstellingen die verder gaan dan \"langzaam\" hebben een afnemend rendement en worden niet aanbevolen.", + "preview_preset_head": "Voorbeeld van codering voorinstelling", + "preview_seg_count_desc": "Aantal segmenten in voorbeeldbestanden.", + "preview_seg_count_head": "Aantal segmenten in voorbeeld", + "preview_seg_duration_desc": "Duur van elk voorbeeldsegment, in seconden.", + "preview_seg_duration_head": "Voorbeeld Segment Duur", + "sprites": "Scene Scrubber Sprites", + "sprites_tooltip": "Sprites (voor de scene scrubber)", + "transcodes": "Transcodes", + "transcodes_tooltip": "MP4-conversies van niet-ondersteunde video-indelingen", + "video_previews": "Voorbeelden", + "video_previews_tooltip": "Videovoorbeelden die worden afgespeeld wanneer u over een scène beweegt" + }, + "scenes_found": "{count} scenes gevonden", + "scrape_entity_query": "{entity_type} Schraper Query", + "scrape_entity_title": "{entity_type} Schraper Resultaten", + "scrape_results_existing": "Bestaande", + "scrape_results_scraped": "Geschraapt", + "set_image_url_title": "Afbeelding URL", + "unsaved_changes": "Niet-opgeslagen wijzigingen gaan verloren. Weet je zeker dat je wilt vertrekken?" + }, + "dimensions": "Dimensies", + "director": "Regisseur", + "display_mode": { + "grid": "Rooster", + "list": "Lijst", + "tagger": "Label", + "unknown": "Onbekend", + "wall": "Muur" + }, + "donate": "Doneer", + "dupe_check": { + "description": "Niveaus onder 'Exact' kunnen langer duren om te berekenen. Valse positieven kunnen ook worden geretourneerd bij lagere nauwkeurigheidsniveaus.", + "found_sets": "{setCount, plural, one{# set duplicaten gevonden.} other {# sets van duplicaten gevonden.}}", + "options": { + "exact": "Precies", + "high": "Hoog", + "low": "Laag", + "medium": "Medium" + }, + "search_accuracy_label": "Zoek accuraatheid", + "title": "Dubbele Scènes" + }, + "duration": "Looptijd", + "effect_filters": { + "aspect": "Aspect", + "blue": "Blauw", + "blur": "Waas", + "brightness": "Helderheid", + "contrast": "Contrast", + "gamma": "Gamma", + "green": "Groen", + "hue": "Tint", + "name": "Filters", + "name_transforms": "Transformeren", + "red": "Rood", + "reset_filters": "Reset Filters", + "reset_transforms": "Reset Transformaties", + "rotate": "Roteren", + "rotate_left_and_scale": "Naar links draaien en schalen", + "rotate_right_and_scale": "Naar rechts draaien en schalen", + "saturation": "Saturatie", + "scale": "Schaal", + "warmth": "Warmte" + }, + "ethnicity": "Etniciteit", + "eye_color": "Oogkleur", + "fake_tits": "Neppe Tieten", + "false": "Vals", + "favourite": "Favoriet", + "file": "bestand", + "file_info": "Bestandsinformatie", + "file_mod_time": "Bestandsmodificatie tijd", + "files": "bestanden", + "filesize": "Bestands Groote", + "filter": "Filter", + "filter_name": "Filter Naam", + "filters": "Filters", + "framerate": "Frame snelheid", + "frames_per_second": "{value} frames per seconde", + "galleries": "Galerijen", + "gallery": "Galerij", + "gallery_count": "Galerij aantal", + "gender": "Geslacht", + "gender_types": { + "FEMALE": "Vrouw", + "INTERSEX": "Intersex", + "MALE": "Man", + "NON_BINARY": "Non-Binar", + "TRANSGENDER_FEMALE": "Transgender Vrouw", + "TRANSGENDER_MALE": "Transgender Man" + }, + "hair_color": "Haar kleur", + "hasMarkers": "Heeft Markeringen", + "height": "Hoogte", + "help": "Help", + "image": "Afbeelding", + "image_count": "Afbeelding aantal", + "images": "Afbeeldingen", + "include_parent_tags": "Bovenliggende tags opnemen", + "include_sub_studios": "Dochteronderneming studio's opnemen", + "include_sub_tags": "Neem sub-tags op", + "instagram": "Instagram", + "interactive": "Interactief", + "interactive_speed": "Interactieve snelheid", + "isMissing": "Is Missende", + "library": "Bibliotheek", + "loading": { + "generic": "Laden…" + }, + "marker_count": "Marker Aantal", + "markers": "Markers", + "measurements": "Afmetingen", + "media_info": { + "audio_codec": "Audio Codec", + "checksum": "Checksum", + "downloaded_from": "Gedownload van", + "hash": "Hash", + "interactive_speed": "Interactieve snelheid", + "performer_card": { + "age": "{age} {years_old}", + "age_context": "{age} {years_old} in deze scène" + }, + "phash": "PHash", + "stream": "Stream", + "video_codec": "Video Codec" + }, + "megabits_per_second": "{value} megabits per seconde", + "metadata": "Metadata", + "movie": "Film", + "movie_scene_number": "Film Scene Nummer", + "movies": "Films", + "name": "Naam", + "new": "Nieuw", + "none": "Geen", + "o_counter": "O-Teller", + "operations": "Operaties", + "organized": "Georganiseerd", + "pagination": { + "first": "Eerste", + "last": "Laatste", + "next": "Volgende", + "previous": "Vorige" + }, + "parent_of": "Ouder van {children}", + "parent_studios": "Ouderstudio's", + "parent_tag_count": "Aantal bovenliggende tags", + "parent_tags": "Bovenlagentlabels", + "part_of": "Onderdeel van {parent}", + "path": "Pad", + "performer": "Performer", + "performerTags": "Peformer Labels", + "performer_count": "Performer Aantal", + "performer_image": "Performer Afbeelding", + "performers": "Performers", + "piercings": "Piercings", + "queue": "Wachtrij", + "random": "Willekeurig", + "rating": "Beoordeling", + "resolution": "Resolutie", + "scene": "Scène", + "sceneTagger": "Scene Labelen", + "sceneTags": "Scene Labels", + "scene_count": "Scene Aantal", + "scene_id": "Scene ID", + "scenes": "Scènes", + "scenes_updated_at": "Scène geüpdatet op", + "search_filter": { + "add_filter": "Filter Toevoegen", + "name": "Filter", + "saved_filters": "Opgeslagen filters", + "update_filter": "Filter Updaten" + }, + "seconds": "Seconden", + "settings": "Instellingen", "setup": { + "confirm": { + "almost_ready": "We zijn bijna klaar om de configuratie te voltooien. Bevestig de volgende instellingen. U kunt op terug klikken om iets onjuists te wijzigen. Als alles er goed uitziet, klikt u op Bevestigen om uw systeem aan te maken.", + "configuration_file_location": "Locatie configuratiebestand:", + "database_file_path": "Pad naar databasebestand", + "default_db_location": "/stash-go.sqlite", + "default_generated_content_location": "/generated", + "generated_directory": "Genereerde map", + "nearly_there": "Bijna Daar!", + "stash_library_directories": "Stash bibliotheekmappen" + }, + "creating": { + "creating_your_system": "Uw systeem aanmaken", + "ffmpeg_notice": "Als ffmpeg nog niet in je pad staat, heb dan even geduld terwijl stash het downloadt. Bekijk de console-uitvoer om de downloadvoortgang te zien." + }, + "errors": { + "something_went_wrong": "Oh nee! Er is iets fout gegaan!", + "something_went_wrong_description": "Als dit lijkt op een probleem met je invoer, ga je gang en klik je op Terug om ze op te lossen. Breng anders een bug aan op {githubLink} of zoek hulp in de {discordLink}.", + "something_went_wrong_while_setting_up_your_system": "Er is iets misgegaan tijdens het instellen van uw systeem. Dit is de fout die we hebben ontvangen: {error}" + }, + "github_repository": "Github repository", + "migrate": { + "backup_database_path_leave_empty_to_disable_backup": "Pad naar back-up database (leeg laten om back-up uit te schakelen):", + "backup_recommended": "Het wordt aanbevolen een back-up van uw bestaande database te maken voordat u migreert. We kunnen dit voor je doen door een kopie van je database te maken naar {defaultBackupPath}.", + "migrating_database": "Database migreren", + "migration_failed": "Migratie gefaald", + "migration_failed_error": "De volgende fout is opgetreden tijdens het migreren van de database:", + "migration_failed_help": "Breng de nodige correcties aan en probeer het opnieuw. Breng anders een bug aan op {githubLink} of zoek hulp in de {discordLink}.", + "migration_irreversible_warning": "Het schemamigratieproces is niet omkeerbaar. Zodra de migratie is uitgevoerd, is uw database incompatibel met eerdere versies van stash.", + "migration_required": "Migratie benodigd", + "perform_schema_migration": "Schemamigratie uitvoeren", + "schema_too_old": "Uw huidige stashdatabase is schemaversie {databaseSchema} en moet worden gemigreerd naar versie {appSchema}. Deze versie van Stash werkt niet zonder de database te migreren." + }, + "paths": { + "database_filename_empty_for_default": "database bestandsnaam (leeg als standaard)", + "description": "Vervolgens moeten we bepalen waar we je pornocollectie kunnen vinden, waar we de stash-database en gegenereerde bestanden kunnen opslaan. Deze instellingen kunnen indien nodig later worden gewijzigd.", + "path_to_generated_directory_empty_for_default": "pad naar gegenereerde map (standaard leeg)", + "set_up_your_paths": "Stel je paden in", + "stash_alert": "Er zijn geen bibliotheekpaden geselecteerd. Er kan dan geen media worden gescand in Stash. Weet je zeker dat?", + "where_can_stash_store_its_database": "Waar kan Stash zijn database opslaan?", + "where_can_stash_store_its_database_description": "Stash gebruikt een sqlite-database om je porno-metadata op te slaan. Standaard wordt dit aangemaakt als stash-go.sqlite in de map die je configuratiebestand bevat. Als u dit wilt wijzigen, voert u een absolute of relatieve (ten opzichte van de huidige werkdirectory) bestandsnaam in.", + "where_can_stash_store_its_generated_content": "Waar kan Stash de gegenereerde inhoud opslaan?", + "where_can_stash_store_its_generated_content_description": "Om thumbnails, previews en sprites aan te bieden, genereert Stash afbeeldingen en video's. Dit omvat ook transcodes voor niet-ondersteunde bestandsindelingen. Standaard zal Stash een generated directory aanmaken in de directory die uw configuratiebestand bevat. Als u wilt wijzigen waar deze gegenereerde media wordt opgeslagen, voert u een absoluut of relatief (ten opzichte van de huidige werkmap) pad in. Stash maakt deze map aan als deze nog niet bestaat.", + "where_is_your_porn_located": "Waar staat je porno?", + "where_is_your_porn_located_description": "Voeg mappen toe die uw pornovideo's en afbeeldingen bevatten. Stash gebruikt deze mappen om video's en afbeeldingen te vinden tijdens het scannen." + }, + "stash_setup_wizard": "Stash-installatiewizard", "success": { "getting_help": "Help", - "in_app_manual_explained": "Het is aanbevolen om de in-app handleiding te bekijken, het is raadpleegbaar via het {icon} icoontje rechts-boven" - } + "help_links": "Als je problemen tegenkomt of vragen of suggesties hebt, open dan gerust een issue in de {githubLink}, of vraag het de community in de {discordLink}.", + "in_app_manual_explained": "Het is aanbevolen om de in-app handleiding te bekijken, het is raadpleegbaar via het {icon} icoontje rechts-boven", + "next_config_step_one": "U wordt vervolgens naar de configuratiepagina geleid. Op deze pagina kunt u aanpassen welke bestanden u wilt opnemen en uitsluiten, een gebruikersnaam en wachtwoord instellen om uw systeem te beschermen en een heleboel andere opties.", + "next_config_step_two": "Als je tevreden bent met deze instellingen, kun je beginnen met het scannen van je inhoud naar Stash door op {localized_task} te klikken en vervolgens op {localized_scan} te klikken.", + "open_collective": "Bekijk onze {open_collective_link} om te zien hoe jij kunt bijdragen aan de verdere ontwikkeling van Stash.", + "support_us": "Ondersteun ons", + "thanks_for_trying_stash": "Bedankt voor het proberen van Stash!", + "welcome_contrib": "We verwelkomen ook bijdragen in de vorm van code (bugfixes, verbeteringen en nieuwe functies), testen, bugrapporten, verbeterings- en functieverzoeken en gebruikersondersteuning. Details zijn te vinden in het gedeelte Bijdrage van de in-app-handleiding.", + "your_system_has_been_created": "Succes! Uw systeem is aangemaakt!" + }, + "welcome": { + "config_path_logic_explained": "Stash probeert eerst zijn configuratiebestand (config.yml) uit de huidige werkdirectory te vinden, en als het daar niet gevonden wordt, valt het terug naar $HOME/.stash/config. yml (in Windows is dit %USERPROFILE%\\.stash\\config.yml). Je kunt Stash ook laten lezen uit een specifiek configuratiebestand door het uit te voeren met de opties -c of --config .", + "in_current_stash_directory": "In de $HOME/.stash map", + "in_the_current_working_directory": "In de huidige werkdirectory", + "next_step": "Met dat alles uit de weg, als u klaar bent om door te gaan met het opzetten van een nieuw systeem, kiest u waar u uw configuratiebestand wilt opslaan en klikt u op Volgende.", + "store_stash_config": "Waar wil jij je Stash-configuratie opslaan?", + "unable_to_locate_config": "Als je dit leest, kan Stash geen bestaande configuratie vinden. Deze wizard leidt u door het proces van het opzetten van een nieuwe configuratie.", + "unexpected_explained": "Als je dit scherm onverwachts krijgt, probeer dan Stash opnieuw op te starten in de juiste werkmap of met de -c vlag." + }, + "welcome_specific_config": { + "config_path": "Stash gebruikt het volgende configuratiebestandspad: {path}", + "next_step": "Wanneer u klaar bent om door te gaan met het instellen van een nieuw systeem, klikt u op Volgende.", + "unable_to_locate_specified_config": "Als je dit leest, kan Stash het opgegeven configuratiebestand op de opdrachtregel of in de omgeving niet vinden. Deze wizard leidt u door het proces van het opzetten van een nieuwe configuratiebestand." + }, + "welcome_to_stash": "Welkom bij Stash" }, + "stash_id": "Stash ID", + "stash_ids": "Stash IDs", + "stats": { + "image_size": "Afbeelding groote", + "scenes_duration": "Scene duur", + "scenes_size": "Scene groote" + }, + "status": "Status: {statusText}", + "studio": "Studio", + "studio_depth": "Niveaus (leeg voor iedereen)", + "studios": "Studios", + "sub_tag_count": "Sub-Label aantal", + "sub_tag_of": "Sub-tag van {parent}", + "sub_tags": "Sub-Tags", + "subsidiary_studios": "Onderliggende Studio's", + "synopsis": "Synopsis", + "tag": "Label", + "tag_count": "Label Aantal", + "tags": "Labels", + "tattoos": "Tattoos", + "title": "Titel", + "toast": { + "added_entity": "Toegevoegd {entity}", + "added_generation_job_to_queue": "Generatietaak toegevoegd aan wachtrij", + "created_entity": "{entity} Aangemaakt", + "default_filter_set": "Standaard filterset", + "delete_entity": "Verwijder {count, plural, one {{singularEntity}} other {{pluralEntity}}}", + "delete_past_tense": "Verwijderd {count, plural, one {{singularEntity}} other {{pluralEntity}}}", + "generating_screenshot": "Screenshot Genereren…", + "merged_tags": "Samengevoegde Labels", + "rescanning_entity": "Opnieuw scannen van {count, plural, one {{singularEntity}} other {{pluralEntity}}}…", + "saved_entity": "Opgeslagen {entity}", + "started_auto_tagging": "Autotagging gestart", + "started_generating": "Genereren gestart", + "started_importing": "Importeren gestart", + "updated_entity": "Ge-updatet {entity}" + }, + "total": "Totaal", "true": "Waar", "twitter": "Twitter", + "up-dir": "Een directory omhoog", "updated_at": "Bijgewerkt op", "url": "URL", + "videos": "Video's", "weight": "Gewicht", "years_old": "jaar oud" } diff --git a/ui/v2.5/src/locales/pt-BR.json b/ui/v2.5/src/locales/pt-BR.json index be21a0e19..3d6be2863 100644 --- a/ui/v2.5/src/locales/pt-BR.json +++ b/ui/v2.5/src/locales/pt-BR.json @@ -489,7 +489,6 @@ "galleries": "Galerias", "gallery": "Galeria", "gallery_count": "Contagem de galeria", - "gender": "Gênero", "hair_color": "Cor do cabelo", "hasMarkers": "Possui marcadores", "height": "Altura", diff --git a/ui/v2.5/src/locales/ru-RU.json b/ui/v2.5/src/locales/ru-RU.json index 52a04b96d..edb526a5e 100644 --- a/ui/v2.5/src/locales/ru-RU.json +++ b/ui/v2.5/src/locales/ru-RU.json @@ -13,9 +13,12 @@ "cancel": "Отмена", "clean": "Очистить", "clear": "Очистить", + "clear_back_image": "Очистить заднее изображение", + "clear_front_image": "Очистить лицевое изображение", "clear_image": "Очистить Изображение", "close": "Закрыть", "confirm": "Подтвердить", + "continue": "Продолжить", "create": "Создать", "create_entity": "Создать {entityType}", "create_marker": "Создать Маркер", @@ -23,6 +26,7 @@ "delete": "Удалить", "delete_entity": "Удалить {entityType}", "delete_file": "Удалить файл", + "delete_generated_supporting_files": "Удалить сгенерированные вспомогательные файлы", "disallow": "Запретить", "download": "Скачать", "download_backup": "Скачать Бэкап", @@ -38,6 +42,7 @@ "generate": "Сгенерировать", "generate_thumb_default": "Сгенерировать миниатюру по умолчанию", "generate_thumb_from_current": "Сгенерировать миниатюру из текущей", + "hash_migration": "миграция хэшей", "hide": "Скрыть", "identify": "Идентифицировать", "ignore": "Игнорировать", @@ -45,6 +50,320 @@ "import_from_file": "Импорт из файла", "merge": "Слияние", "merge_from": "Слияние из", - "merge_into": "Слияние в" - } + "merge_into": "Слияние в", + "next_action": "Вперёд", + "not_running": "не выполняется", + "open_random": "Открыть Случайный", + "overwrite": "Перезаписать", + "play_random": "Воспроизвести Случайный", + "play_selected": "Воспроизвести выбранный", + "preview": "Предпросмотр", + "previous_action": "Назад", + "refresh": "Обновить", + "reload_plugins": "Перезагрузить плагины", + "reload_scrapers": "Перезагрузить скрейперы", + "remove": "Удалить", + "rename_gen_files": "Переименовать сгенерированные файлы", + "rescan": "Сканировать заново", + "reshuffle": "Перемешать", + "running": "выполняется", + "save": "Сохранить", + "save_delete_settings": "Использовать эти настройки по умолчанию во время удаления", + "save_filter": "Сохранить фильтр", + "scan": "Сканировать", + "scrape": "Скрейпить", + "scrape_query": "Запрос скрейпера", + "scrape_scene_fragment": "Скрейпить по фрагменту", + "scrape_with": "Скрейпить используя…", + "search": "Поиск", + "select_all": "Выбрать все", + "select_folders": "Выбрать папки", + "select_none": "Ничего не выбрать", + "selective_auto_tag": "Выборочный автоматический тэгинг", + "selective_clean": "Выборочная чистка", + "selective_scan": "Выборочнное сканирование", + "set_as_default": "Установить по умолчанию", + "set_back_image": "Заднее изображение…", + "set_front_image": "Лицевое изображение…", + "set_image": "Установить изображение…", + "show": "Показать", + "skip": "Пропустить", + "stop": "Остановить", + "tasks": { + "dry_mode_selected": "Выбран режим симуляции. Фактического удаления не будет, только запись в журнал.", + "import_warning": "Вы уверены, что хотите импортировать? Это приведет к удалению базы данных и повторному импорту из экспортированных метаданных." + }, + "temp_disable": "Временно отключить…", + "temp_enable": "Временно включить…", + "use_default": "Использовать по умолчанию", + "view_random": "Смотреть случайный" + }, + "actions_name": "Действия", + "age": "Возраст", + "aliases": "Псевдонимы", + "all": "все", + "ascending": "По возрастанию", + "average_resolution": "Среднее разрешение", + "birth_year": "Год рождения", + "birthdate": "Дата рождения", + "bitrate": "Битрейт", + "career_length": "Продолжительность карьеры", + "component_tagger": { + "config": { + "active_instance": "Активная инстанция Stash-устройства:", + "blacklist_label": "Черный список", + "query_mode_auto": "Автоматически", + "query_mode_auto_desc": "Использует метаданные, если они есть, или имя файла", + "query_mode_dir": "Директория", + "query_mode_filename": "Имя файла", + "query_mode_label": "Режим Очереди", + "query_mode_metadata": "Метаданные", + "query_mode_path": "Путь", + "set_tag_label": "Установить теги", + "source": "Источник" + }, + "noun_query": "Запрос", + "results": { + "duration_unknown": "Длительность неизвестна", + "unnamed": "Безымянный" + }, + "verb_matched": "Совпавший" + }, + "config": { + "about": { + "new_version_notice": "[НОВЫЙ]", + "version": "Версия" + }, + "categories": { + "about": "О программе", + "interface": "Интерфейс", + "logs": "Журнал", + "plugins": "Плагины", + "scraping": "Скрейпинг", + "security": "Безопасность", + "services": "Сервисы", + "system": "Система", + "tasks": "Задачи", + "tools": "Инструменты" + }, + "dlna": { + "network_interfaces": "Интерфейсы" + }, + "general": { + "auth": { + "authentication": "Аутентификация", + "credentials": { + "heading": "Реквизиты для входа" + }, + "password": "Пароль", + "username": "Имя пользователя" + }, + "hashing": "Хэширование", + "logging": "Ведение журнала", + "scraping": "Скрейпинг", + "video_head": "Видео" + }, + "library": { + "exclusions": "Исключения" + }, + "plugins": { + "hooks": "Триггеры" + }, + "scraping": { + "scraper": "Скрейпер", + "scrapers": "Скрейперы", + "supported_urls": "Ссылки" + }, + "stashbox": { + "endpoint": "Конечная точка", + "name": "Имя" + }, + "system": { + "transcoding": "Транскодирование" + }, + "tasks": { + "identify": { + "field": "Поле", + "heading": "Идентифицировать", + "source": "Источник", + "sources": "Источники", + "strategy": "Стратегия" + }, + "maintenance": "Техническое обслуживание", + "migrations": "Миграции" + }, + "tools": { + "scene_filename_parser": { + "filename": "Имя файла" + } + }, + "ui": { + "editing": { + "heading": "Редактирование" + }, + "images": { + "heading": "Изображения" + }, + "language": { + "heading": "Язык" + }, + "preview_type": { + "options": { + "video": "Видео" + } + } + } + }, + "configuration": "Настройки", + "country": "Страна", + "criterion": { + "value": "Значение" + }, + "criterion_modifier": { + "between": "между", + "equals": "есть", + "excludes": "исключает", + "includes": "включает" + }, + "custom": "Пользовательский", + "date": "Дата", + "descending": "По убыванию", + "detail": "Дополнительная информация", + "details": "Подробности", + "dialogs": { + "delete_entity_desc": "{count, plural, one {Вы уверены, что хотите удалить этот {singularEntity}? Если файл также не будет удален, этот {singularEntity} будет повторно добавлен при сканировании.} other {Вы уверены, что хотите удалить эти {pluralEntity}? Если файлы также не будут удалены, эти {pluralEntity} будут повторно добавлены при выполнении сканирования.}}", + "export_title": "Экспорт", + "lightbox": { + "display_mode": { + "original": "Оригинал" + }, + "options": "Параметры", + "scroll_mode": { + "zoom": "Увеличить" + } + }, + "merge_tags": { + "destination": "Назначение", + "source": "Источник" + }, + "scene_gen": { + "transcodes": "Транскоды", + "video_previews": "Превью" + }, + "scrape_results_existing": "Существующий" + }, + "dimensions": "Размер", + "director": "Режиссер", + "display_mode": { + "grid": "Сетка", + "list": "Список", + "tagger": "Теггер", + "unknown": "Неизвестный", + "wall": "Стена" + }, + "donate": "Пожертвование", + "dupe_check": { + "options": { + "exact": "Точный", + "high": "Высокий", + "low": "Нижний", + "medium": "Средний" + } + }, + "duration": "Продолжительность", + "effect_filters": { + "aspect": "Аспект", + "blue": "Синий", + "blur": "Размытие", + "brightness": "Яркость", + "contrast": "Контраст", + "gamma": "Гамма", + "green": "Зеленый", + "hue": "Оттенок", + "name": "Фильтры", + "red": "Красный", + "rotate": "Поворот", + "saturation": "Насыщенность", + "scale": "Масштаб", + "warmth": "Тепло" + }, + "ethnicity": "Этническая принадлежность", + "false": "Нет", + "favourite": "Избранный", + "file": "файл", + "files": "файлы", + "filter": "Фильтр", + "filters": "Фильтры", + "galleries": "Галлереи", + "gallery": "Галлерея", + "height": "Рост", + "help": "Помощь", + "image": "Изображение", + "images": "Изображения", + "instagram": "Instagram", + "interactive": "Интерактивно", + "library": "Библиотека", + "loading": { + "generic": "Загрузка…" + }, + "markers": "Маркеры", + "measurements": "Размеры", + "media_info": { + "checksum": "Контрольная сумма", + "hash": "Хэш", + "phash": "PHash", + "stream": "Стрим" + }, + "metadata": "Метаданные", + "movie": "Фильм", + "movies": "Фильмы", + "name": "Имя", + "new": "Новый", + "none": "Никакой", + "o_counter": "О-Счетчик", + "operations": "Операции", + "organized": "Организован", + "pagination": { + "first": "Первый", + "last": "Последний", + "next": "Следующий", + "previous": "Предыдущий" + }, + "path": "Путь", + "performer": "Исполнитель", + "performers": "Исполнители", + "piercings": "Пирсинг", + "queue": "Очередь", + "random": "Случайный", + "rating": "Рейтинг", + "resolution": "Разрешение", + "scene": "Сцена", + "scenes": "Сцены", + "search_filter": { + "name": "Фильтр" + }, + "seconds": "Секунды", + "settings": "Настройки", + "setup": { + "paths": { + "where_can_stash_store_its_generated_content_description": "Чтобы предоставить эскизы, превью и спрайты, Stash генерирует изображения и видео. Сюда также входят транскоды для неподдерживаемых форматов файлов. По умолчанию Stash создает каталог generated в каталоге, содержащем ваш файл конфигурации. Если вы хотите изменить место хранения сгенерированного мультимедиа, введите абсолютный или относительный (по отношению к текущему рабочему каталогу) путь. Stash создаст этот каталог, если он еще не существует." + }, + "welcome": { + "config_path_logic_explained": "Stash сначала пытается найти свой файл конфигурации (config.yml) в текущем рабочем каталоге, и, если он не находит его там, возвращается к $HOME/.stash/config.yml (в Windows это будет %USERPROFILE%\\.stash\\config.yml). Вы также можете заставить Stash читать из определенного файла конфигурации, запустив его с параметрами -c <путь к файлу конфигурации> или --config <путь к файлу конфигурации>." + } + }, + "studio": "Студия", + "studios": "Студии", + "sub_tags": "Под-Теги", + "synopsis": "Резюме", + "tag": "Тег", + "tags": "Теги", + "tattoos": "Татуировки", + "title": "Заголовок", + "total": "Всего", + "true": "Да", + "twitter": "Twitter", + "url": "Ссылка", + "videos": "Видео", + "weight": "Вес" } diff --git a/ui/v2.5/src/locales/sv-SE.json b/ui/v2.5/src/locales/sv-SE.json index afe59cc1b..98711230d 100644 --- a/ui/v2.5/src/locales/sv-SE.json +++ b/ui/v2.5/src/locales/sv-SE.json @@ -692,6 +692,14 @@ "gallery": "Galleri", "gallery_count": "Antal Gallerier", "gender": "Kön", + "gender_types": { + "FEMALE": "Kvinna", + "INTERSEX": "Intersex", + "MALE": "Man", + "NON_BINARY": "Icke-binär", + "TRANSGENDER_FEMALE": "Trans Kvinna", + "TRANSGENDER_MALE": "Trans Man" + }, "hair_color": "Hårfärg", "hasMarkers": "Har markörer", "height": "Längd", diff --git a/ui/v2.5/src/locales/tr-TR.json b/ui/v2.5/src/locales/tr-TR.json index 567cee482..28ef1a157 100644 --- a/ui/v2.5/src/locales/tr-TR.json +++ b/ui/v2.5/src/locales/tr-TR.json @@ -3,7 +3,7 @@ "add": "Ekle", "add_directory": "Dizin Ekle", "add_entity": "{entityType} Ekle", - "add_to_entity": "{entityType}'a Ekle", + "add_to_entity": "Buraya Ekle: {entityType}", "allow": "İzin Ver", "allow_temporarily": "Geçici olarak izin ver", "apply": "Uygula", @@ -13,14 +13,14 @@ "cancel": "İptal", "clean": "Temizle", "clear": "Temizle", - "clear_back_image": "Arka resmi temizle", - "clear_front_image": "Ön resmi temizle", - "clear_image": "Resmi Temizle", + "clear_back_image": "Arka kapak resmini kaldır", + "clear_front_image": "Ön kapak resmini kaldır", + "clear_image": "Resmi Kaldır", "close": "Kapat", "confirm": "Onayla", "continue": "Devam et", - "create": "Yarat", - "create_entity": "{entityType} Yarat", + "create": "Oluştur", + "create_entity": "{entityType} Oluştur", "create_marker": "Yer İmi Oluştur", "created_entity": "{entity_type}: {entity_name} oluşturuldu", "delete": "Sil", @@ -29,23 +29,30 @@ "delete_generated_supporting_files": "Oluşturulan ek dosyaları sil", "disallow": "İzin verme", "download": "İndir", - "download_backup": "Yedeği İndir", + "download_backup": "Yedekleme Dosyasını İndir", "edit": "Düzenle", "export": "Dışa Aktar…", "export_all": "Tümünü dışa aktar…", "find": "Bul", - "finish": "Tamamla", + "finish": "Bitir", "from_file": "Dosyadan…", - "from_url": "Linkten…", + "from_url": "Internetten…", "full_export": "Tam Dışa Aktarım", "full_import": "Tam İçe Aktarım", + "generate": "Oluştur", + "generate_thumb_default": "Varsayılan küçük resim oluştur", + "generate_thumb_from_current": "Mevcut görüntüden küçük resim oluştur", + "hash_migration": "hash taşıma", "hide": "Gizle", "identify": "Tanımla", "ignore": "Yoksay", "import": "İçe Aktar…", "import_from_file": "Dosyadan içe aktar", "merge": "Birleştir", + "merge_from": "Buradan birleştir", + "merge_into": "Bununla birleştir", "next_action": "Sonraki", + "not_running": "çalışmıyor", "open_random": "Rastgele Aç", "overwrite": "Üzerine Yaz", "play_random": "Rastgele Oynat", @@ -53,29 +60,32 @@ "preview": "Önizleme", "previous_action": "Geri", "refresh": "Yenile", - "reload_plugins": "Eklentileri yenile", + "reload_plugins": "Eklentileri yeniden yükle", + "reload_scrapers": "Veri Toplayıcıları yeniden yükle", "remove": "Kaldır", "rename_gen_files": "Oluşturulan dosyaları yeniden adlandır", - "rescan": "Tekrar Tara", - "reshuffle": "Sırasını değiştir", + "rescan": "Yeniden tara", + "reshuffle": "Tekrar karıştır", + "running": "çalışıyor", "save": "Kaydet", "save_delete_settings": "Artık silerken bu seçenekleri kullan", "save_filter": "Filtreyi kaydet", "scan": "Tara", - "scrape": "Karşıdan İndir", - "scrape_query": "Karşıdan indirme sorgusu", - "scrape_scene_fragment": "Sahneye göre indir", - "scrape_with": "İndirme aracı…", + "scrape": "Veri Topla", + "scrape_query": "Veri Toplama sorgusu", + "scrape_scene_fragment": "Sahneye göre Veri Topla", + "scrape_with": "Bununla Veri Topla…", "search": "Ara", "select_all": "Tümünü Seç", "select_folders": "Dizinleri seç", "select_none": "Hiçbirini Seçme", + "selective_auto_tag": "Seçerek Otomatik Etiketle", "selective_clean": "Seçerek Temizle", "selective_scan": "Seçerek Tara", - "set_as_default": "Öntanımlı olarak ayarla", - "set_back_image": "Arka resim…", - "set_front_image": "Ön resim…", - "set_image": "Resim ayarla…", + "set_as_default": "Varsayılan olarak ayarla", + "set_back_image": "Arka kapak resmi…", + "set_front_image": "Ön kapak resmi…", + "set_image": "Resim seç…", "show": "Göster", "skip": "Geç", "stop": "Dur", @@ -87,7 +97,7 @@ "temp_disable": "Geçici olarak devre dışı bırak…", "temp_enable": "Geçici olarak etkinleştir…", "use_default": "Varsayılanı kullan", - "view_random": "Rastgele Bak" + "view_random": "Rastgele Göster" }, "actions_name": "Eylemler", "age": "Yaş", @@ -99,10 +109,12 @@ "birth_year": "Doğum Yılı", "birthdate": "Doğum Tarihi", "bitrate": "Bit Hızı", + "career_length": "Kariyer Süresi", "component_tagger": { "config": { "active_instance": "Aktif stash-box:", - "blacklist_label": "Karaliste", + "blacklist_desc": "Kara listeye alınan kelimeler sorguya eklenmez. Sözkonusu kelimeler kurallı ifadelerdir (regex) ve büyük-küçük harf ayrımına duyarlı değillerdir. Belirli karakterler ters bölü işaretiyle ayrılmalıdır: {chars_require_escape}", + "blacklist_label": "Kara liste", "query_mode_auto": "Otomatik", "query_mode_auto_desc": "Varsa üst veri veya dosya adını kullanır", "query_mode_dir": "Dizin", @@ -114,11 +126,11 @@ "query_mode_metadata_desc": "Sadece üst veri bilgisini kullanır", "query_mode_path": "Konum", "query_mode_path_desc": "Dosya yolunu kullanır", - "set_cover_desc": "Eğer bulunursa sahne kapağını değiştir.", + "set_cover_desc": "Eğer bulunursa sahne kapak resmini değiştir.", "set_cover_label": "Sahne için kapak resmi seç", "set_tag_desc": "Sahneye etiket ekle (varolan etiketlerle birleştir veya üzerine yaz).", "set_tag_label": "Etiketleri düzenle", - "show_male_desc": "Erkek oyuncuların etiketlenebilirliğini aç/kapat.", + "show_male_desc": "Erkek oyunculara etiket ekleme işlemini aç/kapat.", "show_male_label": "Erkek oyuncuları göster", "source": "Kaynak" }, @@ -126,6 +138,7 @@ "results": { "duration_off": "Süre en az {number} saniye hatalı", "duration_unknown": "Süre bilinmiyor", + "fp_found": "{fpCount, plural, =0 {Yeni parmak izi eşleşmesi bulunamadı} other {# yeni parmak izi eşleşmesi bulundu}}", "fp_matches": "Süre eşleşiyor", "fp_matches_multi": "Süre {matchCount}/{durationsLength} parmak iziyle eşleşiyor", "hash_matches": "{hash_type} eşleşiyor", @@ -136,7 +149,11 @@ "unnamed": "İsimsiz" }, "verb_match_fp": "Parmak İzlerini Eşleştir", - "verb_matched": "Eşleşti" + "verb_matched": "Eşleşti", + "verb_scrape_all": "Tümü için Veri Topla", + "verb_submit_fp": "{fpCount, plural, one{# Parmak izi} other{# Parmak izlerini}} gönder", + "verb_toggle_config": "{toggle} {configuration}", + "verb_toggle_unmatched": "{toggle} eşleşmeyen sahneler" }, "config": { "about": { @@ -161,7 +178,7 @@ "logs": "Kayıtlar", "metadata_providers": "Üst Veri Sağlayıcılar", "plugins": "Eklentiler", - "scraping": "Veri Çekme", + "scraping": "Veri Toplama", "security": "Güvenlik", "services": "Servisler", "system": "Sistem", @@ -169,11 +186,11 @@ "tools": "Araçlar" }, "dlna": { - "allow_temp_ip": "{tempIP} adresine izin ver", + "allow_temp_ip": "{tempIP} IP adresine izin ver", "allowed_ip_addresses": "İzinli IP adresleri", "default_ip_whitelist": "Öntanımlı izinli IP'ler", "default_ip_whitelist_desc": "DLNA erişimi için izin verilen IP'ler. {wildcard} kullanarak tüm IP adreslerine izin verebilirsiniz.", - "enabled_by_default": "Öntanımlı olarak etkin", + "enabled_by_default": "Varsayılan olarak etkin", "network_interfaces": "Arayüzler", "network_interfaces_desc": "DLNA erişimi için izin verilecek ağ arayüzleri. Boş bırakmak tüm arayüzlerden yayın yapılmasını sağlar. Değişikliğin ardından DLNA yeniden başlatılmalıdır.", "recent_ip_addresses": "Son kullanılan IP adresleri", @@ -198,7 +215,7 @@ "log_http_desc": "HTTP erişimini terminalde gösterir. Yeniden başlatma gerektirir.", "log_to_terminal": "Terminale kaydet", "log_to_terminal_desc": "Dosyanın yanı sıra terminale de kayıt gösterir. Dosyaya kayıt kapalı ise her zaman etkindir. Yeniden başlatma gerektirir.", - "maximum_session_age": "Maksimum Oturum Yaşı", + "maximum_session_age": "Maksimum Oturum Süresi", "maximum_session_age_desc": "Oturumun sonlanmaması için izin verilen süre, saniye cinsinden.", "password": "Parola", "password_desc": "Stash'a erişim için parola. Kullanıcı yetkilendirme yapmak istemiyorsanız boş bırakın", @@ -210,6 +227,7 @@ }, "cache_location": "Önbellek dizininin konumu", "cache_path_head": "Önbellek Dizini", + "calculate_md5_and_ohash_desc": "oshash'in yanısıra MD5 checksum'ı da hesapla. Bu özelliği etkinleştirmek tarama işlemini yavaşlatacaktır. MD5 hesaplamasını devre dışı bırakmak için dosya adı imzası (hash) oshash olarak ayarlanmalıdır.", "calculate_md5_and_ohash_label": "Videolar için MD5 hesapla", "check_for_insecure_certificates": "Güvensiz sertifikaları kontrol et", "check_for_insecure_certificates_desc": "Bazı siteler güvensiz SSL sertifikası kullanıyor olabilir. Bu seçenek kapalı ise veri çekici sertifika doğrulama adımını yapmaz. Veri çekerken sertifika hatası alıyorsanız bu işareti kaldırabilirsiniz.", @@ -218,18 +236,663 @@ "create_galleries_from_folders_desc": "Seçili ise resim içeren dizinlerden galeriler oluşturur.", "create_galleries_from_folders_label": "Resim içeren dizinlerden galeri oluştur", "db_path_head": "Veritabanı Yolu", - "directory_locations_to_your_content": "İçeriğiniz için dizin lokasyonları" + "directory_locations_to_your_content": "İçeriğiniz için dizin lokasyonları", + "excluded_image_gallery_patterns_desc": "Tarama ve Temizleme işlemine eklenmeyecek Resim ve Galeri dosyaları/dosya konumları için kurallı ifadeler (Regexp)", + "excluded_image_gallery_patterns_head": "Dışta tutulan Resim/Galeri Kuralları", + "excluded_video_patterns_desc": "Tarama ve Temizleme işlemine eklenmeyecek Video dosyaları/dosya konumları için kurallı ifadeler (Regexp)", + "excluded_video_patterns_head": "Dışta tutulan Video Kuralları", + "gallery_ext_desc": "Galeri ZIP dosyaları olarak tanımlanacak dosya uzantıları listesi (virgülle ayrılmış).", + "gallery_ext_head": "Galeri ZIP dosya Uzantıları", + "generated_file_naming_hash_desc": "Oluşturulacak dosya isimleri için MD5 veya oshash kullanın. Bu değeri değiştirmek, tüm sahneler için MD5/oshash hesaplaması gerektirir. Bu değeri değiştirdikten sonra mevcut tüm ek dosyalar yeniden oluşturulacak veya yer değiştirecektir. Yer değiştirme işlemleri için Görevler sayfasını ziyaret edin.", + "generated_file_naming_hash_head": "Oluşturulan dosya adı imzası", + "generated_files_location": "Oluşturulan ek dosyalar için dizin konumu (yer işaretleri, sahne önizlemeler, küçük resimler vb.)", + "generated_path_head": "Oluşturulan Dizin Konumu", + "hashing": "Dosya imzası hesaplama (Hashing)", + "image_ext_desc": "Resim olarak tanımlanacak dosya uzantıları listesi (virgülle ayrılmış).", + "image_ext_head": "Resim Dosyası Uzantıları", + "include_audio_desc": "Önizleme oluştururken videodan sesleri de ekler.", + "include_audio_head": "Sesi ekle", + "logging": "Kayıt Tutma", + "maximum_streaming_transcode_size_desc": "Dönüştürülmüş video yayınları için maksimum boyut", + "maximum_streaming_transcode_size_head": "Maksimum yayınlanacak dönüştürülmüş video boyutu", + "maximum_transcode_size_desc": "Dönüştürülmüş videoların maksimum boyutu", + "maximum_transcode_size_head": "Maksimum dönüştürülmüş video boyutu", + "metadata_path": { + "description": "Tam dışa verme veya Tam içe aktarma işlemi için dizin konumu", + "heading": "Üst Veri Konumu" + }, + "number_of_parallel_task_for_scan_generation_desc": "Otomatik algılama için 0 olarak ayarlayın. Uyarı: Gerekenden fazla görev çalıştırmak %100 CPU kullanımına, performans düşüşüne ve diğer sorunlara yol açar.", + "number_of_parallel_task_for_scan_generation_head": "Eş zamanlı olarak çalıştırılacak Tarama/Oluşturma görev sayısı", + "parallel_scan_head": "Eş zamanlı Tarama/Oluşturma", + "preview_generation": "Önizleme Oluşturma", + "scraper_user_agent": "Veri Toplama Kimliği (User Agent)", + "scraper_user_agent_desc": "Veri Toplama sırasında HTTP istekleri için kullanılacak Kimliği (User Agent)", + "scrapers_path": { + "description": "Veri Toplama yapılandırma dosyaları için dizin konumu", + "heading": "Veri Toplama Dosyaları Dizin Konumu" + }, + "scraping": "Veri Toplama", + "sqlite_location": "SQLite veritabanı için dizin konumu (değiştirirseniz yeniden başlatma gerekir)", + "video_ext_desc": "Video olarak işlem görecek dosya uzantı listesi (virgülle ayrılmış).", + "video_ext_head": "Video Uzantıları", + "video_head": "Video" + }, + "library": { + "exclusions": "Dışta Tutulanlar", + "gallery_and_image_options": "Galeri ve Resim seçenekleri", + "media_content_extensions": "Medya içerik uzantıları" + }, + "logs": { + "log_level": "Kayıt Tutma Seviyesi" + }, + "plugins": { + "hooks": "Kancalar", + "triggers_on": "Tetikleyiciler açık" + }, + "scraping": { + "entity_metadata": "{entityType} Üst Verisi", + "entity_scrapers": "{entityType} veri toplayıcıları", + "excluded_tag_patterns_desc": "Veri toplama sonuçlarına eklenmeyecek etiketler için kurallı ifadeler", + "excluded_tag_patterns_head": "Dışta Tutulan Etiket Kuralları", + "scraper": "Veri Toplayıcı", + "scrapers": "Veri Toplayıcılar", + "search_by_name": "Ada göre ara", + "supported_types": "Desteklenen türler", + "supported_urls": "Internet Adresleri" + }, + "stashbox": { + "add_instance": "Stash-box oturumu ekle", + "api_key": "API anahtarı", + "description": "Stash-box, sahne ve oyuncuları parmak izleri ve dosya adlarına göre otomatik olarak tanımlamaya yardımcı olur.\nBağlantı noktası and API anahtarı, hesabınız sekmesinde stash-box oturumu bölümünden bulunabilir. Birden fazla oturum açacaksanız isim eklemeniz gerekmektedir.", + "endpoint": "Bağlantı Noktası", + "graphql_endpoint": "GraphQL bağlantı noktası", + "name": "İsim", + "title": "Stash-box Bağlantı Noktaları" + }, + "system": { + "transcoding": "Dönüştürme" + }, + "tasks": { + "added_job_to_queue": "{operation_name} işlem kuyruğuna eklendi", + "auto_tag": { + "auto_tagging_all_paths": "Tüm konumlar otomatik olarak etiketleniyor", + "auto_tagging_paths": "Bu konumlar otomatik olarak etiketleniyor" + }, + "auto_tag_based_on_filenames": "İçeriği dosya adlarına göre otomatik etiketle.", + "auto_tagging": "Otomatik Etiketleme", + "backing_up_database": "Veritabanı yedekleniyor", + "backup_and_download": "Veritabanını yedekler ve yedek dosyasını kaydetmenizi sağlar.", + "backup_database": "Veritabanı diziniyle aynı yere {filename_format} biçimini kullanarak yedekler", + "cleanup_desc": "Eksik dosyaları kontrol eder ve veritabanından siler. Bu geri döndürülemez bir işlemdir.", + "data_management": "Veri yönetimi", + "defaults_set": "Varsayılanlar ayarlandı. Bundan sonra Görevler sayfasındaki {action} düğmesi bu varsayılanları kullanacak.", + "dont_include_file_extension_as_part_of_the_title": "Dosya uzantısını başlıkta gösterme", + "empty_queue": "Şu anda çalışan bir görev yok.", + "export_to_json": "Veritabanı içeriğini JSON olarak Üst Veri dizinine kaydeder.", + "generate": { + "generating_from_paths": "Bu dizinlerdeki sahneler için oluşturuluyor", + "generating_scenes": "{num} {scene} için oluşturuluyor" + }, + "generate_desc": "Resim, hareketli resim, video, vtt ve diğer ek dosyaları oluştur.", + "generate_phashes_during_scan": "Algısal dosya imzası oluştur", + "generate_phashes_during_scan_tooltip": "Tekrarlayan verilerin silinmesi ve sahne tanımlaması için.", + "generate_previews_during_scan": "Hareketli resim önizlemeleri oluştur", + "generate_previews_during_scan_tooltip": "Önizleme türü Hareketli Görüntü olarak ayarlanmışsa hareketli WebP önizlemeleri oluştur.", + "generate_sprites_during_scan": "Basit hareketli resimler oluştur", + "generate_thumbnails_during_scan": "Resimler için küçük önizleme resimleri oluştur", + "generate_video_previews_during_scan": "Önizlemeler oluştur", + "generate_video_previews_during_scan_tooltip": "Fare sahne üzerinde iken önizleme için hareketli video oluştur", + "generated_content": "Oluşturulan İçerik", + "identify": { + "and_create_missing": "ve eksik olanı oluştur", + "create_missing": "Eksik olanı oluştur", + "default_options": "Varsayılan Seçenekler", + "description": "Sahne üst verisini stash-box ve veri toplama kaynakları kullanarak otomatik olarak belirle.", + "explicit_set_description": "Bu seçenekler yok saymayı özellikle belirtmediğiniz sürece kullanılacaktır.", + "field": "Alan", + "field_behaviour": "{strategy} {field}", + "field_options": "Alan Seçenekleri", + "heading": "Tanımla", + "identifying_from_paths": "Bu dizin konumlarından sahneler tanımlanıyor", + "identifying_scenes": "{num} {scene} tanımlanıyor", + "include_male_performers": "Erkek oyuncuları dahil et", + "set_cover_images": "Kapak resimlerini ayarla", + "set_organized": "Düzenlenmiş olarak işaretle", + "source": "Kaynak", + "source_options": "{source} Seçenekleri", + "sources": "Kaynaklar", + "strategy": "Strateji" + }, + "import_from_exported_json": "Üst Veri dizinine kaydedilen JSON dosyasından içeri aktar. Mevcut veritabanını tamamen siler.", + "incremental_import": "Tarih sırasına göre dışa aktarılan ZIP dosyasından içeri aktar.", + "job_queue": "Görev Kuyruğu", + "maintenance": "Bakım", + "migrate_hash_files": "Oluşturulan dosya adı imzası, varolan dosya imza biçimleriyle değiştirildikten sonra kullanıldı.", + "migrations": "Yer Değiştirmeler", + "only_dry_run": "Sadece genel tarama yap. Hiçbir şeyi silme", + "plugin_tasks": "Eklenti Görevleri", + "scan": { + "scanning_all_paths": "Tüm dizin konumlarını tarıyor", + "scanning_paths": "Bu dizin konumlarını tarıyor" + }, + "scan_for_content_desc": "Yeni içerik için tara ve veritabanına ekle.", + "set_name_date_details_from_metadata_if_present": "Gömülü üst veriden isim, tarih ve detayları ayarla" + }, + "tools": { + "scene_duplicate_checker": "Kopya Sahne Denetçisi", + "scene_filename_parser": { + "add_field": "Alan Ekle", + "capitalize_title": "Başlığın ilk harflerini büyük harf yap", + "display_fields": "Alanları göster", + "escape_chars": "Özel harfler ve karakterler için \\ kullan", + "filename": "Dosya adı", + "filename_pattern": "Dosya adı Kuralı", + "ignore_organized": "Düzenlenmiş sahneleri yoksay", + "ignored_words": "Yoksayılan kelimeler", + "matches_with": "{i} ile eşleşen", + "select_parser_recipe": "Derleyici Tarifi Seç", + "title": "Sahne Dosya Adı Derleyicisi", + "whitespace_chars": "Boşluk karakterleri", + "whitespace_chars_desc": "Bu karakterler başlıkta boşluk karakteri ile değiştirilecektir" + }, + "scene_tools": "Sahne Araçları" + }, + "ui": { + "basic_settings": "Temel Seçenekler", + "custom_css": { + "description": "Değişikliklerin geçerli olması için sayfa yeniden yüklenmelidir.", + "heading": "İsteğe Uyarlanmış CSS", + "option_label": "İsteğe Uyarlanmış CSS etkinleştirildi" + }, + "delete_options": { + "description": "Resimleri, galerileri ve sahneleri silerken kullanılacak varsayılan seçenekler.", + "heading": "Silme Seçenekleri", + "options": { + "delete_file": "Varsayılan olarak dosyayı her zaman sil", + "delete_generated_supporting_files": "Varsayılan olarak ek dosyaları her zaman sil" + } + }, + "desktop_integration": { + "desktop_integration": "Masaüstü Bütünleştirmesi", + "skip_opening_browser": "Web Tarayıcısını Açma", + "skip_opening_browser_on_startup": "Başlangıçta web tarayıcısının otomatik olarak açılmasını engeller" + }, + "editing": { + "disable_dropdown_create": { + "description": "Açılır menülerden yeni veri oluşturma özelliğini kapatır", + "heading": "Açılır menüden yeni veri oluşturmayı devre dışı bırak" + }, + "heading": "Düzenleme" + }, + "funscript_offset": { + "description": "Etkileşimli komut oynatmaları için milisaniye cinsinden süre farkı.", + "heading": "Funscript Süre Farkı (ms)" + }, + "handy_connection_key": { + "description": "Etkileşimli sahneler içinHandy bağlantı anahtarı. Bu anahtarı kullanırsanız, Stash izlediğiniz sahne bilgisini handyfeeling.com ile paylaşacaktır", + "heading": "Handy Bağlantı Anahtarı" + }, + "images": { + "heading": "Resimler", + "options": { + "write_image_thumbnails": { + "description": "Resim önizlemelerini oluşturulma anında diske kaydet", + "heading": "Resim önizlemelerini kaydet" + } + } + }, + "interactive_options": "Etkileşimli Sahne Seçenekleri", + "language": { + "heading": "Dil" + }, + "max_loop_duration": { + "description": "Sahne oynatıcısı videoyu yeniden oynatırken ara vereceği süre - Aralıksız yeniden oynatmak için 0 seçin", + "heading": "Yeniden oynatma sırasında ara verilecek maksimum süre" + }, + "menu_items": { + "description": "Gezinti çubuğunda farklı türdeki içerikleri göster veya gizle", + "heading": "Menü Öğeleri" + }, + "performers": { + "options": { + "image_location": { + "description": "Varsayılan oyuncu resimleri için isteğe bağlı dizin konumu. Varolan konumu kullanmak için boş bırakın", + "heading": "İsteğe Bağlı Oyuncu Resim Konumu" + } + } + }, + "preview_type": { + "description": "Duvar görünümündeki öğeler için ayarlar", + "heading": "Önizleme Türü", + "options": { + "animated": "Hareketli Resim", + "static": "Hareketsiz Resim", + "video": "Video" + } + }, + "scene_list": { + "heading": "Sahne Listesi", + "options": { + "show_studio_as_text": "Stüdyoları düz metin olarak göster" + } + }, + "scene_player": { + "heading": "Sahne Oynatıcısı", + "options": { + "auto_start_video": "Videoları otomatik başlat", + "auto_start_video_on_play_selected": { + "description": "Seçili olanı veya Sahneler sayfasından rastgele seçilen videoları otomatik başlat", + "heading": "Seçilen oynatılırken videoyu otomatik başlat" + }, + "continue_playlist_default": { + "description": "Kuyruktaki video bitince sıradaki sahneyi oynat", + "heading": "Varsayılan olarak oynatma listesine devam et" + } + } + }, + "scene_wall": { + "heading": "Sahne / Yer İmi Duvarı", + "options": { + "display_title": "Başlık ve etiketleri göster", + "toggle_sound": "Sesi etkinleştir" + } + }, + "slideshow_delay": { + "description": "Galeri sayfasında Duvar görünümü seçilirse slayt gösterisi yapılabilir", + "heading": "Slayt Gösterisi Geciktirme" + }, + "title": "Kullanıcı Arayüzü" } }, - "media_info": { - "downloaded_from": "İndirildiği Yer" + "configuration": "Yapılandırma", + "countables": { + "files": "{count, plural, one {Dosya} other {Dosyalar}}", + "galleries": "{count, plural, one {Galeri} other {Galeriler}}", + "images": "{count, plural, one {Resim} other {Resimler}}", + "markers": "{count, plural, one {Yer İmi} other {Yer İmleri}}", + "movies": "{count, plural, one {Film} other {Filmler}}", + "performers": "{count, plural, one {Oyuncu} other {Oyuncular}}", + "scenes": "{count, plural, one {Sahne} other {Sahneler}}", + "studios": "{count, plural, one {Stüdyo} other {Stüdyolar}}", + "tags": "{count, plural, one {Etiket} other {Etiketler}}" }, + "country": "Ülke", + "cover_image": "Kapak Resmi", + "created_at": "Oluşturulma Tarihi", + "criterion": { + "greater_than": "Büyüktür", + "less_than": "Küçüktür", + "value": "Değer" + }, + "criterion_modifier": { + "between": "arasında", + "equals": "eşittir", + "excludes": "içermeyen", + "format_string": "{criterion} {modifierString} {valueString}", + "greater_than": "büyüktür", + "includes": "içeren", + "includes_all": "tümünü içeren", + "is_null": "boş olan", + "less_than": "küçüktür", + "matches_regex": "kurallı ifadeyle eşleşen", + "not_between": "arasında olmayan", + "not_equals": "eşit değildir", + "not_matches_regex": "kurallı ifadeyle eşleşmeyen", + "not_null": "boş olmayan" + }, + "custom": "İsteğe Bağlı", + "date": "Tarih", + "death_date": "Ölüm Tarihi", + "death_year": "Ölüm Yılı", + "descending": "Azalan", + "detail": "Ayrıntı", + "details": "Ayrıntılar", + "developmentVersion": "Geliştirme Sürümü", + "dialogs": { + "aliases_must_be_unique": "takma adlar benzersiz olmalıdır", + "delete_alert": "Bu {count, plural, one {{singularEntity}} other {{pluralEntity}}} kalıcı olarak silinecektir:", + "delete_confirm": "Bunu silmek istediğinizden emin misiniz: {entityName}?", + "delete_entity_desc": "{count, plural, one {Bunu silmek istediğinizden emin misiniz: {entityName}? Dosyayı bilgisayarınızdan silmediğiniz sürece, yeniden tarama işleminde bu {singularEntity} tekrar veritabanına eklenecektir.} other {Bunları silmek istediğinizden emin misiniz: {pluralEntity}? Dosyayı bilgisayarınızdan silmediğiniz sürece, yeniden tarama işleminde bu {pluralEntity} tekrar veritabanına eklenecektir.}}", + "delete_entity_title": "{count, plural, one {{singularEntity} Sil} other {{pluralEntity} Sil}}", + "delete_galleries_extra": "...ek olarak herhangi bir galeriye eklenmemiş tüm resim dosyaları.", + "delete_gallery_files": "Herhangi bir galeriye eklenmemiş tüm galeri dizinlerini ve ZIP dosyalarını sil.", + "delete_object_desc": "{count, plural, one {Bunu: {singularEntity}} other {Bunları: {pluralEntity}}} silmek istediğinizden emin misiniz?", + "delete_object_overflow": "…ve {count} diğer {count, plural, one {{singularEntity}} other {{pluralEntity}}}.", + "delete_object_title": "{count, plural, one {{singularEntity}} other {{pluralEntity}}} Sil", + "edit_entity_title": "{count, plural, one {{singularEntity}} other {{pluralEntity}}} Düzenle", + "export_include_related_objects": "Dışa aktarırken bağlantılı nesneleri de ekle", + "export_title": "Dışa Aktar", + "lightbox": { + "delay": "Gecikme (Saniye)", + "display_mode": { + "fit_horizontally": "Yatay olarak sığdır", + "fit_to_screen": "Ekrana sığdır", + "label": "Görüntüleme Modu", + "original": "Orijinal" + }, + "options": "Seçenekler", + "reset_zoom_on_nav": "Resim değiştirirken yakınlaştırma seviyesini sıfırla", + "scale_up": { + "description": "Küçük resimleri ekrana sığdırmak için büyüt", + "label": "Sığdırmak için büyüt" + }, + "scroll_mode": { + "description": "Geçici olarak başka mod kullanmak için Shift tuşuna basın.", + "label": "Kaydırma Modu", + "pan_y": "Y Ekseninde Çevir", + "zoom": "Yakınlaştırma" + } + }, + "merge_tags": { + "destination": "Hedef Noktası", + "source": "Kaynak" + }, + "overwrite_filter_confirm": "Kayıtlı {entityName} sorgusunun üzerine yazmak istediğinizden emin misiniz?", + "scene_gen": { + "force_transcodes": "Dönüştürülmüş video oluşturmayı zorla", + "force_transcodes_tooltip": "Dönüştürülmüş video dosyaları, web tarayıcınız desteklenmeyen bir videoyu görüntülemeye çalışırken oluşturulur. Bu seçeneği işaretlerseniz, web tarayıcınız o videoyu desteklese bile yine de dönüştürülmüş video dosyası oluşturur.", + "image_previews": "Hareketli Resim Önizlemeleri", + "image_previews_tooltip": "Hareketli WebP önizlemeleri, Önizleme Türü sadece Hareketli Resim olarak seçilmişse gereklidir.", + "interactive_heatmap_speed": "Etkileşimli sahneler için ısı haritaları ve hız kayıtları oluştur", + "marker_image_previews": "Yer İmi Hareketli Resim Önizlemeleri", + "marker_image_previews_tooltip": "Hareketli Yer İmi WebP önizlemeleri, Önizleme Türü sadece Hareketli Resim olarak seçilmişse gereklidir.", + "marker_screenshots": "Yer İmi Ekran Görüntüleri", + "marker_screenshots_tooltip": "Yer İmi hareketsiz JPG resimleri, Önizleme Türü sadece Hareketsiz Resim olarak seçilmişse gereklidir.", + "markers": "Yer İmi Önizlemeleri", + "markers_tooltip": "Belirlenen zamandan itibaren başlayan 20 saniyelik videolar.", + "overwrite": "Varolan oluşturulmuş dosyaların üzerine yaz", + "phash": "Algısal dosya imzaları (kopya dosyaları tespit için)", + "preview_exclude_end_time_desc": "Sahne önizlemelerinden son x saniyeyi çıkarır. Bu değer, saniye cinsinden veya toplam sahne uzunluğunun yüzdesi (örneğin %2) cinsinden belirlenebilir.", + "preview_exclude_end_time_head": "Son bölümü çıkar", + "preview_exclude_start_time_desc": "Sahne önizlemelerinden ilk x saniyeyi çıkarır. Bu değer, saniye cinsinden veya toplam sahne uzunluğunun yüzdesi (örneğin %2) cinsinden belirlenebilir.", + "preview_exclude_start_time_head": "İlk bölümü çıkar", + "preview_generation_options": "Önizleme Oluşturma Seçenekleri", + "preview_options": "Önizleme Seçenekleri", + "preview_preset_desc": "Bu ön ayar, oluşturulacak önizleme boyutunu, kalitesini ve düzenleme süresini belirler. \"Yavaş\" dışındaki ayarların kullanılması tavsiye edilmez.", + "preview_preset_head": "Düzenleme ön ayarı önizlemesi", + "preview_seg_count_desc": "Önizleme dosyalarındaki bölüm sayısı.", + "preview_seg_count_head": "Önizlemedeki bölüm sayısı", + "preview_seg_duration_desc": "Saniye cinsinden her önizleme bölümünün süresi.", + "preview_seg_duration_head": "Bölüm önizleme süresi", + "sprites": "Basit Sahne Hareketli Görüntüleri", + "sprites_tooltip": "Hareketli Görüntüler (basit sahne hareketli görüntüleri için)", + "transcodes": "Dönüştürülmüş Videolar", + "transcodes_tooltip": "Desteklenmeyen video biçimleri için MP4 dönüştürmeleri", + "video_previews": "Önizlemeler", + "video_previews_tooltip": "Fare sahne üzerinde iken video önizlemeleri" + }, + "scenes_found": "{count} sahne bulundu", + "scrape_entity_query": "{entity_type} Veri Toplama Sorgusu", + "scrape_entity_title": "{entity_type} Veri Toplama Sonuçları", + "scrape_results_existing": "Mevcut", + "scrape_results_scraped": "Toplanan", + "set_image_url_title": "Resim Internet Adresi", + "unsaved_changes": "Değişiklikler kaydedilmedi. Sayfadan ayrılmak istediğinize emin misiniz?" + }, + "dimensions": "Boyutlar", + "director": "Yönetmen", + "display_mode": { + "grid": "Izgara", + "list": "Liste", + "tagger": "Etiketleyici", + "unknown": "Bilinmeyen", + "wall": "Duvar" + }, + "donate": "Bağış Yap", + "dupe_check": { + "description": "'Mutlak' seviyesinin altındaki seviyeleri hesaplamak çok daha uzun sürebilir. Ayrıca düşük kesinlik seviyelerinde yanlış sonuçlar da alabilirsiniz.", + "found_sets": "{setCount, plural, one{# kopya bulundu.} other {# kopya bulundu.}}", + "options": { + "exact": "Mutlak", + "high": "Yüksek", + "low": "Düşük", + "medium": "Orta" + }, + "search_accuracy_label": "Arama Kesinliği", + "title": "Kopya Sahneler" + }, + "duration": "Süre", + "effect_filters": { + "aspect": "Yön", + "blue": "Mavi", + "blur": "Bulanıklaştır", + "brightness": "Parlaklık", + "contrast": "Zıtlık", + "gamma": "Gamma", + "green": "Yeşil", + "hue": "Ton", + "name": "Filtreler", + "name_transforms": "Dönüşümler", + "red": "Kırmızı", + "reset_filters": "Filtreleri Sıfırla", + "reset_transforms": "Dönüşümleri Sıfırla", + "rotate": "Döndür", + "rotate_left_and_scale": "Sola Döndür & Boyutlandır", + "rotate_right_and_scale": "Sağa Döndür & Boyutlandır", + "saturation": "Canlılık", + "scale": "Boyutlandır", + "warmth": "Sıcaklık" + }, + "ethnicity": "Etnik Köken", + "eye_color": "Göz Rengi", + "fake_tits": "Takma Göğüs", + "false": "Yanlış", + "favourite": "Favori", + "file": "dosya", + "file_info": "Dosya Bilgisi", + "file_mod_time": "Dosya Düzenleme Tarihi", + "files": "dosyalar", + "filesize": "Dosya Boyutu", + "filter": "Filtre", + "filter_name": "Filtre adı", + "filters": "Filtreler", + "framerate": "Resim Karesi Hızı", + "frames_per_second": "Saniyede {value} resim", + "galleries": "Galeriler", + "gallery": "Galeri", + "gallery_count": "Galeri Sayısı", + "hair_color": "Saç Rengi", + "hasMarkers": "Yer İmi Var", + "height": "Boy", + "help": "Yardım", + "image": "Resim", + "image_count": "Resim Sayısı", + "images": "Resimler", + "include_parent_tags": "Bir üst etiketleri de ekle", + "include_sub_studios": "Bağlı stüdyoları da ekle", + "include_sub_tags": "Bir alt etiketleri de ekle", + "instagram": "Instagram", + "interactive": "Etkileşimli", + "interactive_speed": "Etkileşim hızı", + "isMissing": "Eksik", + "library": "Kitaplık", + "loading": { + "generic": "Yükleniyor…" + }, + "marker_count": "Yer İmi Sayısı", + "markers": "Yer İmleri", + "measurements": "Beden Ölçüleri", + "media_info": { + "audio_codec": "Ses Kodlayıcı", + "checksum": "Sağlama Toplamı (checksum)", + "downloaded_from": "İndirildiği Yer", + "hash": "Dosya İmzası (Hash)", + "interactive_speed": "Etkileşim hızı", + "performer_card": { + "age": "{age} {years_old}", + "age_context": "Bu sahnede {age} {years_old}" + }, + "phash": "PHash", + "stream": "Yayınla", + "video_codec": "Video Kodlayıcı" + }, + "megabits_per_second": "Saniyede {value} megabit", "metadata": "Üst Veri", + "movie": "Film", + "movie_scene_number": "Filmdeki Sahne Sırası", + "movies": "Filmler", "name": "İsim", "new": "Yeni", "none": "Hiçbiri", + "o_counter": "O-Sayacı", + "operations": "İşlemler", + "organized": "Düzenlendi", + "pagination": { + "first": "İlk", + "last": "Son", + "next": "Sonraki", + "previous": "Önceki" + }, + "parent_of": "{children} öğesinin üstü", + "parent_studios": "Üst Stüdyolar", + "parent_tag_count": "Üst Etiket Sayısı", + "parent_tags": "Üst Etiketler", + "part_of": "{parent} öğesinin parçası", + "path": "Konum", + "performer": "Oyuncu", + "performerTags": "Oyuncu Etiketleri", + "performer_count": "Oyuncu Sayısı", + "performer_image": "Oyuncu Resmi", + "performers": "Oyuncular", + "piercings": "Piercings", + "queue": "Oynatma Listesi", + "random": "Rastgele", + "rating": "Derecelendirme", + "resolution": "Çözünürlük", + "scene": "Sahne", + "sceneTagger": "Sahne Etiketleyici", + "sceneTags": "Sahne Etiketleri", + "scene_count": "Sahne Sayısı", + "scene_id": "Sahne Kimliği (ID)", + "scenes": "Sahneler", + "scenes_updated_at": "Sahne Güncelleme Tarihi", + "search_filter": { + "add_filter": "Filtre Ekle", + "name": "Filtre", + "saved_filters": "Kaydedilmiş filtreler", + "update_filter": "Filtreyi Güncelle" + }, + "seconds": "Saniye", + "settings": "Ayarlar", + "setup": { + "confirm": { + "almost_ready": "Ayarlamaları neredeyse tamamlamak üzereyiz. Lütfen bu ayarları onaylayın. Eğer düzenleme yapmak isterseniz geri giderek ayarları değiştirebilirsiniz. Herşey tamamsa Onayla tuşuna basın.", + "configuration_file_location": "Yapılandırma dosyası konumu:", + "database_file_path": "Veritabanı dosya konumu", + "default_db_location": "/stash-go.sqlite", + "default_generated_content_location": "/generated", + "generated_directory": "Oluşturulan dizin", + "nearly_there": "Bitmek üzere!", + "stash_library_directories": "Stash kitaplık dizinleri" + }, + "creating": { + "creating_your_system": "Sisteminiz oluşturuluyor", + "ffmpeg_notice": "ffmpeg sisteminizde yüklü değilse lütfen otomatik olarak indirilip yüklenmesini bekleyin. Konsol detayları bölümünden indirme sürecini görebilirsiniz." + }, + "errors": { + "something_went_wrong": "Hata! Birşeyler yanlış gitti!", + "something_went_wrong_description": "Hatanın sizin belirlediğiniz özel ayarlardan kaynaklanıyor olabilir, lütfen geriye giderek o ayarları değiştirin. Tekrar hata alırsanız {githubLink} veya {discordLink} adreslerini kullanarak sorunu bildirin (Şimdilik yalnızca İngilizce).", + "something_went_wrong_while_setting_up_your_system": "Sisteminizi ayarlarken birşeyler yanlış gitti ve bu hata mesajını aldık: {error}" + }, + "github_repository": "Github repository", + "migrate": { + "backup_database_path_leave_empty_to_disable_backup": "Veritabanı yedekleme konumu (yedeklemeyi devre dışı bırakmak için bu alanı boş bırakın):", + "backup_recommended": "Yer değiştirme işlemi yapmadan önce varolan veritabanınızı yedeklemeniz önerilir. Eğer isterseniz otomatik olarak {defaultBackupPath} konumuna veritabanınızın bir yedeğini oluşturabiliriz.", + "migrating_database": "Veritabanı yer değiştirme", + "migration_failed": "Yer değiştirme başarısız", + "migration_failed_error": "Veritabanı yer değiştirilirken bu hatayla karşılaşıldı:", + "migration_failed_help": "Lütfen gerekli düzeltmeleri yapın ve yeniden deneyin. Tekrar hata alırsanız {githubLink} veya {discordLink} adreslerini kullanarak sorunu bildirin (Şimdilik yalnızca İngilizce).", + "migration_irreversible_warning": "Veritabanı şema yer değiştirme işlemi geri alınamaz. Veritabanınız yer değiştirme işlemi tamamlandıktan sonra önceki Stash sürümleriyle uyumsuz olacaktır.", + "migration_required": "Yer değiştirme işlemi gerekli", + "perform_schema_migration": "Şema yer değiştirme işlemi uygula", + "schema_too_old": "Mevcut Stash veritabanı şema sürümünüz {databaseSchema} ve {appSchema} sürümü ile değiştirilmesi gerekiyor. Stash'ın bu sürümü değiştirme işlemi yapılmadan çalışmayacaktır." + }, + "paths": { + "database_filename_empty_for_default": "veritabanı adı (varsayılan için boş bırakın)", + "description": "Sırada porno koleksiyonunuzun hangi dizinde olduğunun, stash veritabanının ve oluşturulan ek dosyaların nereye kaydedileceğinin belirlenmesi var. Bu ayarları sonradan değiştirebilirsiniz.", + "path_to_generated_directory_empty_for_default": "oluşturulan ek dosyalar için dizin konumu (varsayılan için boş bırakın)", + "set_up_your_paths": "Konumlarınızı belirleyin", + "stash_alert": "Herhangi bir kitaplık konumu seçilmedi. Stash'a hiçbir şey eklenmeyecek. Emin misiniz?", + "where_can_stash_store_its_database": "Stash veritabanı nereye kaydedilsin?", + "where_can_stash_store_its_database_description": "Stash porno arşiviniz için SQLite veritabanı kullanır. Bu veritabanı, varsayılan olarak stash-go.sqlite dizininde, yapılandırma dosyasıyla aynı yerde oluşturulur. Bu dizini değiştirmek isterseniz, yapılandırma dosyasının bulunduğu dizinin tam konumunu girin.", + "where_can_stash_store_its_generated_content": "Stash için oluşturulan ek dosyalar nereye kaydedilsin?", + "where_can_stash_store_its_generated_content_description": "Stash, önizlemeler için küçük hareketli resimler ve videolar oluşturur. Ayrıca web tarayıcınızın desteklemediği video dosyaları için dönüştürülmüş video dosyaları da oluşturulur. Bu amaçla, varsayılan olarak yapılandırma dosyasının bulunduğu generated dizini kullanılır. Bu dizini değiştirmek isterseniz, yapılandırma dosyasının bulunduğu dizinin tam konumunu girin. Eğer dizin bulunamazsa otomatik olarak oluşturulacaktır.", + "where_is_your_porn_located": "Porno koleksiyonunuz hangi dizinde?", + "where_is_your_porn_located_description": "Fotoğraf ve videoların bulunduğu dizinleri ekleyin. Stash, tarama sırasında bu dizinleri kullanacaktır." + }, + "stash_setup_wizard": "Stash Kurulum Sihirbazı", + "success": { + "getting_help": "Yardım alın", + "help_links": "Hata mesajları, soru-yorum-görüş-önerileriniz için lütfen {githubLink} veya {discordLink} adreslerini kullanarak bizimle iletişime geçin (Şimdilik yalnızca İngilizce).", + "in_app_manual_explained": "Sağ üst köşede bulunan {icon} simgesine basarak Yardım dokümanını incelemeniz tavsiye edilir (Şimdilik yalnızca İngilizce)", + "next_config_step_one": "Sırada Yapılandırma sayfası var. Bu sayfada hangi dosyaların eklenip eklenmeyeceğini belirleyebilir, güvenlik amacıyla kullanıcı adı ve şifre seçebilir ve daha bir çok seçeneği ayarlayabilirsiniz.", + "next_config_step_two": "Herşey tamamsa {localized_task}, sonrasında {localized_scan} düğmelerine tıklayarak arşivinizi Stash'e ekleyebilirsiniz.", + "open_collective": "Stash geliştiricilerine destek sağlamak için {open_collective_link} adresini ziyaret edebilirsiniz.", + "support_us": "Bizi destekleyin", + "thanks_for_trying_stash": "Stash'i denediğiniz için teşekkürler!", + "welcome_contrib": "Ayrıca her türlü kod katkınızı (hata düzeltme, geliştirme, yeni özellikler ekleme), uygulamayla ilgili her türlü görüş-öneri-yorum-sorunuzu bekliyoruz. Ayrıntıları uygulama içindeki Yardım belgesinde bulabilirsiniz.", + "your_system_has_been_created": "Sistem oluşturma başarılı!" + }, + "welcome": { + "config_path_logic_explained": "Stash (config.yml) yapılandırma dosyasını ilk olarak mevcut dizinde bulmaya çalışır. Eğer bulamazsa, $HOME/.stash/config.yml dizinini (Windows işletim sistemi için %USERPROFILE%\\.stash\\config.yml dizini) araştırır. Öte yandan -c veya --config seçeneklerini kullanarak özelleştirilmiş bir yapılandırma dosyası da kullanabilirsiniz.", + "in_current_stash_directory": "$HOME/.stash dizini altında", + "in_the_current_working_directory": "Mevcut dizinde", + "next_step": "Eğer yeni bir sistem oluşturmak için hazırsanız, yapılandırma dosyasının nereye kaydedileceğini seçin ve Sonraki düğmesine basın.", + "store_stash_config": "Stash yapılandırmasını nereye kaydetmek istiyorsunuz?", + "unable_to_locate_config": "Eğer bunu okuyorsanız, Stash herhangi bir mevcut yapılandırma bulamamış demektir. Bu sihirbaz yeni bir yapılandırma sırasında size yol gösterecektir.", + "unexpected_explained": "Eğer beklenmedik bir şekilde bu ekranı gördüyseniz, Stash uygulamasını doğru dizinden başlatın veya başlatma komutuna -c değişkenini ekleyin." + }, + "welcome_specific_config": { + "config_path": "Stash, yapılandırma dosyası için bu dizini kullanacak: {path}", + "next_step": "Yeni bir sistem oluşturmak için hazır olduğunuzda Sonraki düğmesine basın.", + "unable_to_locate_specified_config": "Eğer bunu okuyorsanız, Stash yapılandırma dosyasını bulamamış demektir. Bu sihirbaz yeni bir yapılandırma sırasında size yol gösterecektir." + }, + "welcome_to_stash": "Stash uygulamasına hoşgeldiniz" + }, + "stash_id": "Stash Kimliği (ID)", + "stash_ids": "Stash Kimlikleri (ID)", + "stats": { + "image_size": "Toplam resim boyutu", + "scenes_duration": "Toplam sahne süresi", + "scenes_size": "Toplam sahne boyutu" + }, + "status": "Durum: {statusText}", + "studio": "Stüdyo", + "studio_depth": "Seviyeler (tümü için boş bırakın)", + "studios": "Stüdyolar", + "sub_tag_count": "Alt Etiket Sayısı", + "sub_tag_of": "{parent} öğesinin alt etiketi", + "sub_tags": "Alt Etiketler", + "subsidiary_studios": "Bağlı Stüdyolar", + "synopsis": "Özet", + "tag": "Etiket", + "tag_count": "Etiket Sayısı", + "tags": "Etiketler", + "tattoos": "Dövmeler", + "title": "Başlık", + "toast": { + "added_entity": "{entity} eklendi", + "added_generation_job_to_queue": "Oluşturma işlemi kuyruğa eklendi", + "created_entity": "{entity} oluşturuldu", + "default_filter_set": "Varsayılan filtre ayarla", + "delete_entity": "{count, plural, one {{singularEntity}} other {{pluralEntity}}} sil", + "delete_past_tense": "{count, plural, one {{singularEntity}} other {{pluralEntity}}} silindi", + "generating_screenshot": "Ekran görüntüsü oluşturuluyor…", + "merged_tags": "Etiketler birleştirildi", + "rescanning_entity": "{count, plural, one {{singularEntity}} other {{pluralEntity}}} yeniden taranıyor…", + "saved_entity": "{entity} kaydedildi", + "started_auto_tagging": "Otomatik etiketleme başladı", + "started_generating": "Oluşturma işlemi başladı", + "started_importing": "İçe aktarma başladı", + "updated_entity": "{entity} güncellendi" + }, + "total": "Toplam", + "true": "Doğru", + "twitter": "Twitter", "up-dir": "Bir dizin üste çık", "updated_at": "Güncellenme Zamanı", - "url": "URL", - "videos": "Videolar" + "url": "Internet Adresi (URL)", + "videos": "Videolar", + "weight": "Kilo", + "years_old": "yaşında" } diff --git a/ui/v2.5/src/locales/zh-CN.json b/ui/v2.5/src/locales/zh-CN.json index 0ce07b141..ede950071 100644 --- a/ui/v2.5/src/locales/zh-CN.json +++ b/ui/v2.5/src/locales/zh-CN.json @@ -691,7 +691,6 @@ "galleries": "图库", "gallery": "图库", "gallery_count": "图库数量", - "gender": "性别", "hair_color": "头发颜色", "hasMarkers": "含有章节标记", "height": "身高", diff --git a/ui/v2.5/src/locales/zh-TW.json b/ui/v2.5/src/locales/zh-TW.json index da84aeb75..a7cd0e38d 100644 --- a/ui/v2.5/src/locales/zh-TW.json +++ b/ui/v2.5/src/locales/zh-TW.json @@ -691,7 +691,9 @@ "galleries": "圖庫", "gallery": "圖庫", "gallery_count": "圖庫數量", - "gender": "性別", + "gender": { + "NON_BINARY": "非二元" + }, "hair_color": "頭髮顏色", "hasMarkers": "含有章節標記", "height": "身高", From db55b1746a69ae811042d9d8771959134b0cc184 Mon Sep 17 00:00:00 2001 From: InfiniteTF Date: Wed, 12 Jan 2022 01:34:11 +0100 Subject: [PATCH 20/80] Show country flags for ISO coded countries (#2230) --- ui/v2.5/src/utils/country.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/v2.5/src/utils/country.ts b/ui/v2.5/src/utils/country.ts index 20555abb8..d9961a388 100644 --- a/ui/v2.5/src/utils/country.ts +++ b/ui/v2.5/src/utils/country.ts @@ -20,8 +20,10 @@ const fuzzyDict: Record = { const getISOCountry = (country: string | null | undefined) => { if (!country) return null; - const code = fuzzyDict[country] ?? Countries.getAlpha2Code(country, "en"); - if (!code) return null; + const code = + fuzzyDict[country] ?? Countries.getAlpha2Code(country, "en") ?? country; + // Check if code is valid alpha2 iso + if (!Countries.alpha2ToAlpha3(code)) return null; return { code, From 51bb602dc21f0ed686e26c7278b84a705f133593 Mon Sep 17 00:00:00 2001 From: InfiniteTF Date: Sun, 16 Jan 2022 00:14:31 +0100 Subject: [PATCH 21/80] Fix vite manifest.json crash (#2237) --- ui/v2.5/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/v2.5/index.html b/ui/v2.5/index.html index dd15fa776..60140dd63 100755 --- a/ui/v2.5/index.html +++ b/ui/v2.5/index.html @@ -13,7 +13,7 @@ manifest.json provides metadata used when your web app is installed on a user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ --> - + Stash From 4bbf511954e1b255ef4c9726f2d31a53135ef5c8 Mon Sep 17 00:00:00 2001 From: InfiniteTF Date: Sun, 16 Jan 2022 00:24:25 +0100 Subject: [PATCH 22/80] Prevent creation of dupe filters (#2233) --- ui/v2.5/src/components/List/AddFilterDialog.tsx | 6 ++++++ ui/v2.5/src/hooks/ListHook.tsx | 1 + 2 files changed, 7 insertions(+) diff --git a/ui/v2.5/src/components/List/AddFilterDialog.tsx b/ui/v2.5/src/components/List/AddFilterDialog.tsx index d67e523b9..a370fab76 100644 --- a/ui/v2.5/src/components/List/AddFilterDialog.tsx +++ b/ui/v2.5/src/components/List/AddFilterDialog.tsx @@ -37,6 +37,7 @@ interface IAddFilterProps { onCancel: () => void; filterOptions: ListFilterOptions; editingCriterion?: Criterion; + existingCriterions: Criterion[]; } export const AddFilterDialog: React.FC = ({ @@ -44,6 +45,7 @@ export const AddFilterDialog: React.FC = ({ onCancel, filterOptions, editingCriterion, + existingCriterions, }) => { const defaultValue = useRef(); @@ -206,6 +208,10 @@ export const AddFilterDialog: React.FC = ({ const thisOptions = [NoneCriterionOption] .concat(filterOptions.criterionOptions) + .filter( + (c) => + !existingCriterions.find((ec) => ec.criterionOption.type === c.type) + ) .map((c) => { return { value: c.type, diff --git a/ui/v2.5/src/hooks/ListHook.tsx b/ui/v2.5/src/hooks/ListHook.tsx index 3df899d48..ed3ea32fa 100644 --- a/ui/v2.5/src/hooks/ListHook.tsx +++ b/ui/v2.5/src/hooks/ListHook.tsx @@ -526,6 +526,7 @@ const RenderList = < onAddCriterion={onAddCriterion} onCancel={onCancelAddCriterion} editingCriterion={editingCriterion} + existingCriterions={filter.criteria} /> )} {newCriterion && From d985e5d9b72dfe581c91e59993520cbae63780aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jan 2022 14:50:38 +1100 Subject: [PATCH 23/80] Bump follow-redirects from 1.14.4 to 1.14.7 in /ui/v2.5 (#2238) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.4 to 1.14.7. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.4...v1.14.7) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ui/v2.5/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/v2.5/yarn.lock b/ui/v2.5/yarn.lock index b7afddd25..fd6abed83 100644 --- a/ui/v2.5/yarn.lock +++ b/ui/v2.5/yarn.lock @@ -3429,9 +3429,9 @@ flexbin@^0.2.0: integrity sha1-ASYwbT1ZX8t9/LhxSbnJWZ/49Ok= follow-redirects@^1.14.0: - version "1.14.4" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz" - integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== + version "1.14.7" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" + integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== form-data@4.0.0: version "4.0.0" From 0388aec94294f4ce18c15ec65d4373067cd817d1 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Thu, 20 Jan 2022 18:10:47 +1100 Subject: [PATCH 24/80] Only update tags if not dirtied (#2241) --- .../Tagger/scenes/StashSearchResult.tsx | 18 +++++++--- ui/v2.5/src/hooks/state.ts | 34 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 ui/v2.5/src/hooks/state.ts diff --git a/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx b/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx index 7debcbc90..f78b80ae7 100755 --- a/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx +++ b/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx @@ -23,6 +23,7 @@ import { OptionalField } from "../IncludeButton"; import { SceneTaggerModalsState } from "./sceneTaggerModals"; import PerformerResult from "./PerformerResult"; import StudioResult from "./StudioResult"; +import { useInitialState } from "src/hooks/state"; const getDurationStatus = ( scene: IScrapedScene, @@ -214,7 +215,9 @@ const StashSearchResult: React.FC = ({ const [excludedFields, setExcludedFields] = useState>( {} ); - const [tagIDs, setTagIDs] = useState(getInitialTags()); + const [tagIDs, setTagIDs, setInitialTagIDs] = useInitialState( + getInitialTags() + ); // map of original performer to id const [performerIDs, setPerformerIDs] = useState<(string | undefined)[]>( @@ -226,8 +229,8 @@ const StashSearchResult: React.FC = ({ ); useEffect(() => { - setTagIDs(getInitialTags()); - }, [getInitialTags]); + setInitialTagIDs(getInitialTags()); + }, [getInitialTags, setInitialTagIDs]); useEffect(() => { setPerformerIDs(getInitialPerformers()); @@ -566,6 +569,13 @@ const StashSearchResult: React.FC = ({ ); + async function onCreateTag(t: GQL.ScrapedTag) { + const newTagID = await createNewTag(t); + if (newTagID !== undefined) { + setTagIDs([...tagIDs, newTagID]); + } + } + const renderTagsField = () => (
@@ -592,7 +602,7 @@ const StashSearchResult: React.FC = ({ variant="secondary" key={t.name} onClick={() => { - createNewTag(t); + onCreateTag(t); }} > {t.name} diff --git a/ui/v2.5/src/hooks/state.ts b/ui/v2.5/src/hooks/state.ts new file mode 100644 index 000000000..00ee32a91 --- /dev/null +++ b/ui/v2.5/src/hooks/state.ts @@ -0,0 +1,34 @@ +import React, { useCallback, Dispatch, SetStateAction } from "react"; + +// useInitialState is an extension of the useState hook. +// It maintains a state, but additionally exposes a setInitialState function. +// When setInitialState is called, the current state is only updated if the current +// state is unchanged from the initial state. This means that the current state will +// only be updated if explicitly called, or if the initial state is changed and the current +// state is not dirty. +export function useInitialState( + initialValue: T +): [T, Dispatch>, Dispatch] { + const [, setInitialValueInternal] = React.useState(initialValue); + const [value, setValue] = React.useState(initialValue); + + const setInitialValue = useCallback((v: T) => { + setInitialValueInternal((currentInitial) => { + if (v === currentInitial) { + return currentInitial; + } + + setValue((currentValue) => { + if (currentInitial === currentValue) { + return v; + } + + return currentValue; + }); + + return v; + }); + }, []); + + return [value, setValue, setInitialValue]; +} From d9fb51f2f99a5cc5b1939cbfa82cc783031cdd38 Mon Sep 17 00:00:00 2001 From: Viacheslav Poturaev Date: Fri, 28 Jan 2022 05:20:05 +0100 Subject: [PATCH 25/80] Reuse static server for multiple requests (#2258) --- pkg/api/server.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/api/server.go b/pkg/api/server.go index e92c3824f..625392ff4 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -184,6 +184,7 @@ func Start(uiBox embed.FS, loginUIBox embed.FS) { } customUILocation := c.GetCustomUILocation() + static := statigz.FileServer(uiBox) // Serve the web app r.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) { @@ -222,7 +223,7 @@ func Start(uiBox embed.FS, loginUIBox embed.FS) { } r.URL.Path = uiRootDir + r.URL.Path - statigz.FileServer(uiBox).ServeHTTP(w, r) + static.ServeHTTP(w, r) } }) From cfd0f1d74feff5f765b175109dfb0c35efdcdf03 Mon Sep 17 00:00:00 2001 From: dumdum7 <95527094+dumdum7@users.noreply.github.com> Date: Fri, 28 Jan 2022 07:15:32 +0100 Subject: [PATCH 26/80] Use
+ setShowDraftModal(false)} + />
); }; diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx index a7a64e461..a6d62f20f 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx @@ -1,7 +1,7 @@ import React from "react"; import { FormattedMessage, FormattedNumber, useIntl } from "react-intl"; import * as GQL from "src/core/generated-graphql"; -import { NavUtils, TextUtils } from "src/utils"; +import { NavUtils, TextUtils, getStashboxBase } from "src/utils"; import { TextField, URLField } from "src/utils/field"; interface ISceneFileInfoPanelProps { @@ -49,7 +49,7 @@ export const SceneFileInfoPanel: React.FC = (