mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Custom localization (#2837)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
@@ -62,6 +62,8 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
|
||||
showStudioAsText
|
||||
css
|
||||
cssEnabled
|
||||
customLocales
|
||||
customLocalesEnabled
|
||||
language
|
||||
imageLightbox {
|
||||
slideshowDelay
|
||||
|
||||
@@ -260,6 +260,10 @@ input ConfigInterfaceInput {
|
||||
css: String
|
||||
cssEnabled: Boolean
|
||||
|
||||
"""Custom Locales"""
|
||||
customLocales: String
|
||||
customLocalesEnabled: Boolean
|
||||
|
||||
"""Interface language"""
|
||||
language: String
|
||||
|
||||
@@ -322,6 +326,10 @@ type ConfigInterfaceResult {
|
||||
css: String
|
||||
cssEnabled: Boolean
|
||||
|
||||
"""Custom Locales"""
|
||||
customLocales: String
|
||||
customLocalesEnabled: Boolean
|
||||
|
||||
"""Interface language"""
|
||||
language: String
|
||||
|
||||
|
||||
@@ -356,6 +356,12 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigI
|
||||
|
||||
setBool(config.CSSEnabled, input.CSSEnabled)
|
||||
|
||||
if input.CustomLocales != nil {
|
||||
c.SetCustomLocales(*input.CustomLocales)
|
||||
}
|
||||
|
||||
setBool(config.CustomLocalesEnabled, input.CustomLocalesEnabled)
|
||||
|
||||
if input.DisableDropdownCreate != nil {
|
||||
ddc := input.DisableDropdownCreate
|
||||
setBool(config.DisableDropdownCreatePerformer, ddc.Performer)
|
||||
|
||||
@@ -141,6 +141,8 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult {
|
||||
showStudioAsText := config.GetShowStudioAsText()
|
||||
css := config.GetCSS()
|
||||
cssEnabled := config.GetCSSEnabled()
|
||||
customLocales := config.GetCustomLocales()
|
||||
customLocalesEnabled := config.GetCustomLocalesEnabled()
|
||||
language := config.GetLanguage()
|
||||
handyKey := config.GetHandyKey()
|
||||
scriptOffset := config.GetFunscriptOffset()
|
||||
@@ -164,6 +166,8 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult {
|
||||
ContinuePlaylistDefault: &continuePlaylistDefault,
|
||||
CSS: &css,
|
||||
CSSEnabled: &cssEnabled,
|
||||
CustomLocales: &customLocales,
|
||||
CustomLocalesEnabled: &customLocalesEnabled,
|
||||
Language: &language,
|
||||
|
||||
ImageLightbox: &imageLightboxOptions,
|
||||
|
||||
@@ -179,6 +179,21 @@ func Start() error {
|
||||
|
||||
http.ServeFile(w, r, fn)
|
||||
})
|
||||
r.HandleFunc("/customlocales", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if !c.GetCustomLocalesEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
// search for custom-locales.json in current directory, then $HOME/.stash
|
||||
fn := c.GetCustomLocalesPath()
|
||||
exists, _ := fsutil.FileExists(fn)
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
http.ServeFile(w, r, fn)
|
||||
})
|
||||
|
||||
r.HandleFunc("/login*", func(w http.ResponseWriter, r *http.Request) {
|
||||
ext := path.Ext(r.URL.Path)
|
||||
|
||||
@@ -138,6 +138,7 @@ const (
|
||||
ContinuePlaylistDefault = "continue_playlist_default"
|
||||
ShowStudioAsText = "show_studio_as_text"
|
||||
CSSEnabled = "cssEnabled"
|
||||
CustomLocalesEnabled = "customLocalesEnabled"
|
||||
|
||||
ShowScrubber = "show_scrubber"
|
||||
showScrubberDefault = true
|
||||
@@ -1056,6 +1057,49 @@ func (i *Instance) GetCSSEnabled() bool {
|
||||
return i.getBool(CSSEnabled)
|
||||
}
|
||||
|
||||
func (i *Instance) GetCustomLocalesPath() string {
|
||||
// use custom-locales.json in the same directory as the config file
|
||||
configFileUsed := i.GetConfigFile()
|
||||
configDir := filepath.Dir(configFileUsed)
|
||||
|
||||
fn := filepath.Join(configDir, "custom-locales.json")
|
||||
|
||||
return fn
|
||||
}
|
||||
|
||||
func (i *Instance) GetCustomLocales() string {
|
||||
fn := i.GetCustomLocalesPath()
|
||||
|
||||
exists, _ := fsutil.FileExists(fn)
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
|
||||
buf, err := os.ReadFile(fn)
|
||||
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func (i *Instance) SetCustomLocales(customLocales string) {
|
||||
fn := i.GetCustomLocalesPath()
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
buf := []byte(customLocales)
|
||||
|
||||
if err := os.WriteFile(fn, buf, 0777); err != nil {
|
||||
logger.Warnf("error while writing %v bytes to %v: %v", len(buf), fn, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Instance) GetCustomLocalesEnabled() bool {
|
||||
return i.getBool(CustomLocalesEnabled)
|
||||
}
|
||||
|
||||
func (i *Instance) GetHandyKey() string {
|
||||
return i.getString(HandyKey)
|
||||
}
|
||||
|
||||
@@ -85,7 +85,10 @@ func TestConcurrentConfigAccess(t *testing.T) {
|
||||
i.Set(ImageLightboxSlideshowDelay, *i.GetImageLightboxOptions().SlideshowDelay)
|
||||
i.GetCSSPath()
|
||||
i.GetCSS()
|
||||
i.GetCustomLocalesPath()
|
||||
i.GetCustomLocales()
|
||||
i.Set(CSSEnabled, i.GetCSSEnabled())
|
||||
i.Set(CSSEnabled, i.GetCustomLocalesEnabled())
|
||||
i.Set(HandyKey, i.GetHandyKey())
|
||||
i.Set(DLNAServerName, i.GetDLNAServerName())
|
||||
i.Set(DLNADefaultEnabled, i.GetDLNADefaultEnabled())
|
||||
|
||||
@@ -29,6 +29,7 @@ import { InteractiveProvider } from "./hooks/Interactive/context";
|
||||
import { ReleaseNotesDialog } from "./components/Dialogs/ReleaseNotesDialog";
|
||||
import { IUIConfig } from "./core/config";
|
||||
import { releaseNotes } from "./docs/en/ReleaseNotes";
|
||||
import { getPlatformURL } from "./core/createClient";
|
||||
|
||||
const Performers = lazy(() => import("./components/Performers/Performers"));
|
||||
const FrontPage = lazy(() => import("./components/FrontPage/FrontPage"));
|
||||
@@ -87,11 +88,24 @@ export const App: React.FC = () => {
|
||||
const defaultMessages = (await locales[defaultMessageLanguage]()).default;
|
||||
const mergedMessages = cloneDeep(Object.assign({}, defaultMessages));
|
||||
const chosenMessages = (await locales[messageLanguage]()).default;
|
||||
mergeWith(mergedMessages, chosenMessages, (objVal, srcVal) => {
|
||||
if (srcVal === "") {
|
||||
return objVal;
|
||||
const res = await fetch(getPlatformURL() + "customlocales");
|
||||
let customMessages = {};
|
||||
try {
|
||||
customMessages = res.ok ? await res.json() : {};
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
mergeWith(
|
||||
mergedMessages,
|
||||
chosenMessages,
|
||||
customMessages,
|
||||
(objVal, srcVal) => {
|
||||
if (srcVal === "") {
|
||||
return objVal;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
setMessages(flattenMessages(mergedMessages));
|
||||
};
|
||||
|
||||
@@ -408,6 +408,36 @@ export const SettingsInterfacePanel: React.FC = () => {
|
||||
}}
|
||||
/>
|
||||
</SettingSection>
|
||||
<SettingSection headingID="config.ui.custom_locales.heading">
|
||||
<BooleanSetting
|
||||
id="custom-locales-enabled"
|
||||
headingID="config.ui.custom_locales.option_label"
|
||||
checked={iface.customLocalesEnabled ?? undefined}
|
||||
onChange={(v) => saveInterface({ customLocalesEnabled: v })}
|
||||
/>
|
||||
|
||||
<ModalSetting<string>
|
||||
id="custom-locales"
|
||||
headingID="config.ui.custom_locales.heading"
|
||||
subHeadingID="config.ui.custom_locales.description"
|
||||
value={iface.customLocales ?? undefined}
|
||||
onChange={(v) => saveInterface({ customLocales: v })}
|
||||
renderField={(value, setValue) => (
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
value={value}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||
setValue(e.currentTarget.value)
|
||||
}
|
||||
rows={16}
|
||||
className="text-input code"
|
||||
/>
|
||||
)}
|
||||
renderValue={() => {
|
||||
return <></>;
|
||||
}}
|
||||
/>
|
||||
</SettingSection>
|
||||
|
||||
<SettingSection headingID="config.ui.interactive_options">
|
||||
<StringSetting
|
||||
|
||||
@@ -5,6 +5,7 @@ After migrating, please run a scan on your entire library to populate missing da
|
||||
* Import/export schema has changed and is incompatible with the previous version.
|
||||
|
||||
### ✨ New Features
|
||||
* Allow overriding UI localisation strings. ([#2837](https://github.com/stashapp/stash/pull/2837))
|
||||
* Populate name from query field when creating new performer/studio/tag/gallery. ([#2701](https://github.com/stashapp/stash/pull/2701))
|
||||
* Added support for identical files. Identical files are assigned to the same scene/gallery/image and can be viewed in File Info. ([#2676](https://github.com/stashapp/stash/pull/2676))
|
||||
* Added support for filtering and sorting by file count. ([#2744](https://github.com/stashapp/stash/pull/2744))
|
||||
|
||||
@@ -26,6 +26,19 @@ The stash UI can be customised using custom CSS. See [here](https://github.com/s
|
||||
|
||||
[Stash Plex Theme](https://github.com/stashapp/stash/wiki/Theme-Plex) is a community created theme inspired by the popular Plex interface.
|
||||
|
||||
## Custom Locales
|
||||
|
||||
The localisation strings can be customised. The master list of default (en-GB) locale strings can be found [here](https://github.com/stashapp/stash/blob/develop/ui/v2.5/src/locales/en-GB.json). The custom locale format is the same as this json file.
|
||||
|
||||
For example, to override the `actions.add_directory` label (which is `Add Directory` by default), you would have the following in the custom locale:
|
||||
|
||||
```
|
||||
{
|
||||
"actions": {
|
||||
"add_directory": "Some other description"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Custom served folders
|
||||
|
||||
|
||||
@@ -427,6 +427,11 @@
|
||||
"heading": "Custom CSS",
|
||||
"option_label": "Custom CSS enabled"
|
||||
},
|
||||
"custom_locales": {
|
||||
"description": "Override individual locale strings. See https://github.com/stashapp/stash/blob/develop/ui/v2.5/src/locales/en-GB.json for the master list. Page must be reloaded for changes to take effect.",
|
||||
"heading": "Custom localisation",
|
||||
"option_label": "Custom localisation enabled"
|
||||
},
|
||||
"delete_options": {
|
||||
"description": "Default settings when deleting images, galleries, and scenes.",
|
||||
"heading": "Delete Options",
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
"scene_filename_parser": {
|
||||
"ignore_organized": "Ignore organized scenes"
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
"custom_locales": {
|
||||
"heading": "Custom localization",
|
||||
"option_label": "Custom localization enabled"
|
||||
}
|
||||
}
|
||||
},
|
||||
"performer_favorite": "Performer Favorited"
|
||||
|
||||
Reference in New Issue
Block a user