Custom localization (#2837)

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
HijackHornet
2022-09-22 11:49:35 +02:00
committed by GitHub
parent c10d53ba8e
commit 74191c73ed
13 changed files with 155 additions and 4 deletions

View File

@@ -62,6 +62,8 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
showStudioAsText
css
cssEnabled
customLocales
customLocalesEnabled
language
imageLightbox {
slideshowDelay

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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())

View File

@@ -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));
};

View File

@@ -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

View File

@@ -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))

View File

@@ -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

View File

@@ -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",

View File

@@ -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"