diff --git a/packages/core/src/Controller/Plugins/Plugins.ts b/packages/core/src/Controller/Plugins/Plugins.ts
index 3dc772aca1..5671d42eda 100644
--- a/packages/core/src/Controller/Plugins/Plugins.ts
+++ b/packages/core/src/Controller/Plugins/Plugins.ts
@@ -187,8 +187,14 @@ export function Plugins(
addPlugin,
findPositions: findPositions,
run() {
- const { apiKey, apiUrl, projectId, observerOptions, tagNewKeys } =
- getInitialOptions();
+ const {
+ apiKey,
+ apiUrl,
+ projectId,
+ observerOptions,
+ tagNewKeys,
+ filterTag,
+ } = getInitialOptions();
instances.ui = plugins.ui?.({
apiKey: apiKey!,
apiUrl: apiUrl!,
@@ -198,6 +204,7 @@ export function Plugins(
findPositions,
onPermanentChange: (data) => events.onPermanentChange.emit(data),
tagNewKeys,
+ filterTag,
});
instances.observer?.run({
@@ -259,13 +266,14 @@ export function Plugins(
}) as BackendGetRecordInternal,
getBackendDevRecord: (async ({ language, namespace }) => {
- const { apiKey, apiUrl, projectId } = getInitialOptions();
+ const { apiKey, apiUrl, projectId, filterTag } = getInitialOptions();
return instances.devBackend?.getRecord({
apiKey,
apiUrl,
projectId,
language,
namespace,
+ filterTag,
...getCommonProps(),
});
}) as BackendGetRecordInternal,
diff --git a/packages/core/src/Controller/State/initState.ts b/packages/core/src/Controller/State/initState.ts
index 2b557ad609..f37c651b23 100644
--- a/packages/core/src/Controller/State/initState.ts
+++ b/packages/core/src/Controller/State/initState.ts
@@ -119,6 +119,11 @@ export type TolgeeOptionsInternal = {
* Specify tags that will be preselected for non-existant keys.
*/
tagNewKeys?: string[];
+
+ /**
+ * Use only keys tagged with one of the listed tags
+ */
+ filterTag?: string[];
};
export type TolgeeOptions = Partial<
diff --git a/packages/core/src/types/plugin.ts b/packages/core/src/types/plugin.ts
index 882e2c11c3..72ff13cee5 100644
--- a/packages/core/src/types/plugin.ts
+++ b/packages/core/src/types/plugin.ts
@@ -14,6 +14,7 @@ export type BackendDevProps = {
apiUrl?: string;
apiKey?: string;
projectId?: number | string;
+ filterTag?: string[];
};
export type CommonProps = {
@@ -160,6 +161,7 @@ export type UiProps = {
changeTranslation: ChangeTranslationInterface;
onPermanentChange: (props: TranslationDescriptor) => void;
tagNewKeys?: string[];
+ filterTag?: string[];
};
export type UiKeyOption = {
diff --git a/packages/i18next/src/__integration/tolgeeUpdating.test.ts b/packages/i18next/src/__integration/tolgeeUpdating.test.ts
index 924a2ca069..416c8eeb5c 100644
--- a/packages/i18next/src/__integration/tolgeeUpdating.test.ts
+++ b/packages/i18next/src/__integration/tolgeeUpdating.test.ts
@@ -6,7 +6,7 @@ import i18n from 'i18next';
import { DevTools, Tolgee } from '@tolgee/web';
import { withTolgee, I18nextPlugin } from '..';
-const API_URL = 'http://localhost';
+const API_URL = 'http://localhost/';
const API_KEY = 'dummyApiKey';
const fetch = mockCoreFetch();
diff --git a/packages/web/src/app/basicTolgee.ts b/packages/web/src/app/basicTolgee.ts
index d7ac5ed871..85e12c9080 100644
--- a/packages/web/src/app/basicTolgee.ts
+++ b/packages/web/src/app/basicTolgee.ts
@@ -15,6 +15,7 @@ export const tolgee = Tolgee()
availableLanguages: ['en', 'cs', 'fr', 'de'],
defaultLanguage: 'en',
tagNewKeys: ['draft'],
+ filterTag: ['test'],
});
export const useTolgee = (events?: TolgeeEvent[]): TolgeeInstance => {
diff --git a/packages/web/src/package/DevBackend.ts b/packages/web/src/package/DevBackend.ts
index d4e1b93fe9..233e895e49 100644
--- a/packages/web/src/package/DevBackend.ts
+++ b/packages/web/src/package/DevBackend.ts
@@ -3,21 +3,36 @@ import { getApiKeyType, getProjectIdFromApiKey } from './tools/decodeApiKey';
function createDevBackend(): BackendDevMiddleware {
return {
- getRecord({ apiUrl, apiKey, language, namespace, projectId, fetch }) {
+ getRecord({
+ apiUrl,
+ apiKey,
+ language,
+ namespace,
+ projectId,
+ filterTag,
+ fetch,
+ }) {
const pId = getProjectIdFromApiKey(apiKey) ?? projectId;
- let url =
- pId !== undefined
- ? `${apiUrl}/v2/projects/${pId}/translations/${language}`
- : `${apiUrl}/v2/projects/translations/${language}`;
+ const url = new URL(apiUrl);
+
+ if (pId !== undefined) {
+ url.pathname = `/v2/projects/${pId}/translations/${language}`;
+ } else {
+ url.pathname = `/v2/projects/translations/${language}`;
+ }
if (namespace) {
- url += `?ns=${namespace}`;
+ url.searchParams.append('ns', namespace);
}
+ filterTag?.forEach((tag) => {
+ url.searchParams.append('filterTag', tag);
+ });
if (getApiKeyType(apiKey) === 'tgpat' && projectId === undefined) {
throw new Error("You need to specify 'projectId' when using PAT key");
}
- return fetch(url, {
+
+ return fetch(url.toString(), {
headers: {
'X-API-Key': apiKey || '',
'Content-Type': 'application/json',
diff --git a/packages/web/src/package/ui/KeyDialog/KeyForm.tsx b/packages/web/src/package/ui/KeyDialog/KeyForm.tsx
index 89d50fdfd3..92d6464abe 100644
--- a/packages/web/src/package/ui/KeyDialog/KeyForm.tsx
+++ b/packages/web/src/package/ui/KeyDialog/KeyForm.tsx
@@ -14,6 +14,7 @@ import { PluralFormCheckbox } from './PluralFormCheckbox';
import { ErrorAlert } from './ErrorAlert';
import { HttpError } from '../client/HttpError';
import { Tooltip } from '../common/Tooltip';
+import { FilterTagMissingInfo } from './Tags/FilterTagMissingInfo';
const ScContainer = styled('div')`
font-family: Rubik, Roboto, Arial;
@@ -53,7 +54,7 @@ const ScKeyHint = styled('span')`
`;
const ScFieldsWrapper = styled('div')`
- margin-top: 20px;
+ margin-top: 10px;
`;
const ScTagsWrapper = styled('div')`
@@ -103,6 +104,7 @@ export const KeyForm = () => {
const fallbackNamespaces = useDialogContext((c) => c.fallbackNamespaces);
const selectedNs = useDialogContext((c) => c.selectedNs);
const permissions = useDialogContext((c) => c.permissions);
+ const filterTagMissing = useDialogContext((c) => c.filterTagMissing);
const screenshotsView = permissions.canViewScreenshots;
const viewPluralCheckbox = permissions.canEditPlural && pluralsSupported;
@@ -181,6 +183,7 @@ export const KeyForm = () => {
Tags
+ {filterTagMissing && }
)}
{ready && viewPluralCheckbox && }
@@ -207,7 +210,7 @@ export const KeyForm = () => {
{
const expanded = _expanded && isPlural;
return (
-
+
{
+ const filterTag = useDialogContext((c) => c.uiProps.filterTag);
+ const filterTagMissing = useDialogContext((c) => c.filterTagMissing);
+
+ if (!filterTagMissing) {
+ return null;
+ }
+
+ if (filterTag)
+ return (
+
+ Missing Required Tag
+ {filterTag.length > 1 ? (
+
+ This app is configured to use only keys tagged with the{' '}
+ . Add one of them to continue.
+
+ ) : (
+
+ This app is configured to use only keys tagged with the{' '}
+ . Add this tag to continue.
+
+ )}
+
+
+ Read more in the docs
+
+
+
+ );
+};
diff --git a/packages/web/src/package/ui/KeyDialog/Tags/MissingTag.tsx b/packages/web/src/package/ui/KeyDialog/Tags/MissingTag.tsx
new file mode 100644
index 0000000000..3621dd7cff
--- /dev/null
+++ b/packages/web/src/package/ui/KeyDialog/Tags/MissingTag.tsx
@@ -0,0 +1,37 @@
+import { styled } from '@mui/material';
+import { useDialogActions } from '../dialogContext';
+
+const StyledTag = styled('div')`
+ display: inline-flex;
+ outline: 0;
+ cursor: default;
+ padding: 1px 8px;
+ border-radius: 12px;
+ align-items: center;
+ font-size: 14px;
+ background: ${({ theme }) => theme.palette.grey[200]};
+ border: 1px solid transparent;
+ max-width: 100%;
+ box-sizing: border-box;
+ border: 1px solid ${({ theme }) => theme.palette.text.secondary};
+ margin: -2px 0px;
+ cursor: pointer;
+ &:focus-within,
+ &:hover {
+ border: 1px solid ${({ theme }) => theme.palette.primary.main};
+ color: ${({ theme }) => theme.palette.primary.main};
+ }
+`;
+
+type Props = {
+ name: string;
+};
+
+export const MissingTag = ({ name }: Props) => {
+ const { setTags } = useDialogActions();
+ function addTag(name: string) {
+ setTags((values) => [...values.filter((t) => t !== name), name]);
+ }
+
+ return addTag(name)}>{name};
+};
diff --git a/packages/web/src/package/ui/KeyDialog/Tags/MissingTagsList.tsx b/packages/web/src/package/ui/KeyDialog/Tags/MissingTagsList.tsx
new file mode 100644
index 0000000000..383c3e4806
--- /dev/null
+++ b/packages/web/src/package/ui/KeyDialog/Tags/MissingTagsList.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { MissingTag } from './MissingTag';
+
+type Props = {
+ tags: string[];
+};
+
+export const MissingTagsList = ({ tags }: Props) => {
+ return (
+ <>
+ {tags.map((tag, index) => {
+ if (index === 0) {
+ return (
+
+
+
+ );
+ } else if (index < tags.length - 1) {
+ return (
+
+ ,
+
+ );
+ } else {
+ return (
+
+ {' '}
+ or
+
+ );
+ }
+ })}
+ >
+ );
+};
diff --git a/packages/web/src/package/ui/KeyDialog/dialogContext/index.ts b/packages/web/src/package/ui/KeyDialog/dialogContext/index.ts
index 3633e4a8ea..ebe7a222a3 100644
--- a/packages/web/src/package/ui/KeyDialog/dialogContext/index.ts
+++ b/packages/web/src/package/ui/KeyDialog/dialogContext/index.ts
@@ -81,11 +81,15 @@ export const [DialogProvider, useDialogActions, useDialogContext] =
const [saving, setSaving] = useState(false);
const [selectedNs, setSelectedNs] = useState(props.namespace);
- const [tags, setTags] = useState([]);
+ const [tags, setTags] = useState(undefined);
const [_isPlural, setIsPlural] = useState();
const [_pluralArgName, setPluralArgName] = useState();
const [submitError, setSubmitError] = useState();
+ const filterTagMissing =
+ Boolean(props.uiProps.filterTag?.length) &&
+ tags &&
+ !props.uiProps.filterTag.find((t) => tags.includes(t));
useEffect(() => {
// reset when key changes
setIsPlural(undefined);
@@ -182,7 +186,10 @@ export const [DialogProvider, useDialogActions, useDialogContext] =
if (firstKey) {
setTags(firstKey?.keyTags?.map((t) => t.name) || []);
} else {
- setTags(props.uiProps.tagNewKeys ?? []);
+ setTags([
+ ...(props.uiProps.filterTag ?? []),
+ ...(props.uiProps.tagNewKeys ?? []),
+ ]);
}
setScreenshots(
firstKey?.screenshots?.map((sc) => ({
@@ -486,7 +493,7 @@ export const [DialogProvider, useDialogActions, useDialogContext] =
screenshotDetail,
linkToPlatform,
keyExists,
- tags,
+ tags: tags || [],
permissions,
canTakeScreenshots,
isPlural,
@@ -495,6 +502,7 @@ export const [DialogProvider, useDialogActions, useDialogContext] =
pluralsSupported,
icuPlaceholders,
submitError,
+ filterTagMissing,
} as const;
const actions = {