g | x | w | all
Bytes Lang Time Link
031Vyxal ṡ211125T055115Zemanresu
076Uiua240130T153047ZJoao-3
061Perl 5 + pl211125T100847ZDom Hast
147TypeScript's type system240127T001605Znoodle p
116Julia220215T223423ZDavid Sc
082Julia 1.0211125T093623ZMarcMush
077BQN211221T180947ZDLosc
129Python 3211203T154432ZAlan Bag
136Rust211125T055608Zbigyihsu
095Python3211126T215845ZAjax1234
nanProlog211129T135259ZEsteis
126SimpleTemplate 0.84211128T055953ZIsmael M
170Batch211127T235742ZNeil
067Zsh211125T092155Zpxeger
077Whython211125T100044Zpxeger
136Python 3211125T011315ZFmbalbue
107C clang m32211125T033156Za stone
144C clang with m32211125T044105ZErikF
073Ruby211125T080736ZG B
091PHP211125T161118ZKaddath
093Java211126T031330ZUnmitiga
086APL+WIN211125T212422ZGraham
nanjq211125T105805Zcnamejj
099Lua211125T180353ZHenrik I
102Python 2211125T170242ZElPedro
100Excel211125T132823ZWernisch
101Dart211125T130203ZBen Broo
089Pari/GP211125T122937Zalephalp
048Charcoal211125T095923ZNeil
063Retina 0.8.2211125T094521ZNeil
094Haskell211125T093457Zovs
04205AB1E211125T092432ZKevin Cr
102R211125T074001Zpajonk
089Vim211125T060337ZDLosc
052Pip211125T044205ZDLosc
089Python 3211125T013109Zhyperneu
047Jelly211125T013442Zcaird co
041Vyxal ṡ211125T012543Zlyxal
082JavaScript211125T013414ZYouserna
077JavaScript211125T014459Ztsh

Vyxal , 31 bytes

ḣ[‛λ¬?Ḣḣ[_?L‹‛÷„]‛λ½|‛is]`Ȯ»...

Try it Online!

ḣ                               # Push the first item (first user) and the rest to the stack
                                # The first item will remain on the bottom of the stack
 [                      ]       # If the rest exists (More than one user)
  ‛λ¬                           # Push 'and'
     ?Ḣ                         # Take all users but the first
       ḣ                        # Push the first item (second user) and rest to the stack
        [       ]               # If the rest exists (≥3 users)
         _                      # Pop the second user
          ?L‹                   # Push the input's length, -1
             ‛÷„                # Push 'others'
                 ‛λ½            # Push 'are'
                    |           # Otherwise (one user)
                     ‛is        # Pop the resulting empty list and push 'is'
                         `Ȯ»... # Push 'typing...'
                                # (ṡ flag) join stack by spaces)

34 bytes flagless.

Uiua, 76 bytes

$"_ typing..."($"_ is"⊔⊢|/$"_ and _ are"|$"_ and _ others are":-1⊃⧻⊢)+1±-2⧻.

Test pad

Perl 5 + -pl, 61 bytes

Thanks to @Xcali for -2!

s;$; @{[!(@_=<>)?is:'and',$#_?@_.' others':@_,are]} typing...

Try it online!

Explanation

The first name is implicitly stored in $_ (via -p) so it's easy to just add to it a string consisting of a leading space and an in-line list @{[...]}, finishing with typing... as that's always needed no matter what. The in-line list will be implicitly joined by $" (space), which allows use of some barewords, saving bytes for the quotes. The rest of the input (<>) is slurped into @_ which, if it's non-empty, results in a list consisting of 'and', and if $#_ is truthy ($#_ is the last array index of @_, which will be 0 if there is only one entry), we use @_ in a scalar context (which is the number of elements i n@_) concatenated with ' others', but if @_ only contains one entry, add it as is. If @_ is empty, we just add the bareword is to the in-line list.

TypeScript's type system, 147 bytes

//@ts-ignore
type F<A,B=A extends[{},...infer B]?B["length"]:0>=`${A[0]} ${B extends 0?"is":`and ${B extends 1?A[1]:`${B} others`} are`} typing...`

Try it at the TS playground!

Julia, 116 bytes

f(l,s=length(l))=replace("x z typing...","x"=>l[1],"z"=>"$(s>1 ? "and $(s>2 ? "$(s-1) others" : l[2]) are" : "is")")

Note that this code does not work with Julia 1.0, thus I can not put it on Try it online. My test code in Julia 1.7 is as follows.

f(l,s=length(l))=replace("x z typing...","x"=>l[1],"z"=>"$(s>1 ? "and $(s>2 ? "$(s-1) others" : l[2]) are" : "is")")

a=["Bubbler"]
b=["no one"]
c=["HyperNeutrino", "user"]
d=["emanresu A", "lyxal", "Jo King"]
e=["pxeger", "null", "null", "null", "null", "null", "null", "null", "null", "null", "null"]
g=["10", "null", "null", "null", "null", "null", "null", "null", "null", "null", "null"]
h=["and", "are"]
k=["is"]
l=["Bubbler is typing", "is", "are", "and"]

println(f(a))
println(f(b))
println(f(c))
println(f(d))
println(f(e))
println(f(g))
println(f(h))
println(f(k))
println(f(l))

which produces the following output

Bubbler is typing...
no one is typing...
HyperNeutrino and user are typing...
emanresu A and 2 others are typing...
pxeger and 10 others are typing...
10 and 10 others are typing...
and and are are typing...
is is typing...
Bubbler is typing and 3 others are typing...

Maybe I can ask the developers of Try it online! to upgrade the Julia version.

Julia 1.0, 82 bytes

t=" typing..."
>(x)="$x is$t"
x>y="$x and $y are$t"
x>y...=x>"$(length(y)) others"

Try it online!

the names are expected as arguments of the function >, for example >("Bubbler"), >("a","b","c")

Julia 0.7, 79 75 bytes

!t=t[(L=end-1;1)]*" $(L<1?:is:"and $(L<2?t[2]:"$L others") are") typing..."

Try it online!

BQN, 77 bytes

⊑∾" typing..."∾˜·(0<≠)◶⟨" is"⋄" and "∾" are"∾˜(1<≠)◶⟨⊑⋄" others"∾˜•fmt∘≠⟩⟩1⊸↓

Anonymous tacit function that takes a list of strings and returns a string. Run it online!

Explanation

(0<≠)◶⟨" is"⋄...⟩1⊸↓
                 1⊸↓  Drop the first element of the argument list
(   )◶                Choose, based on the result of this function:
   ≠                     Length of the remaining list
 0<                      is greater than 0? (1 if so, 0 if not)
      ⟨         ⟩      one of these functions:
       " is"             Return that string
            ⋄         or...


" and "∾" are"∾˜(1<≠)◶⟨⊑⋄...⟩
                (   )◶         Choose, based on the result of this function:
                   ≠              Length of the all-but-the first list
                 1<               is greater than 1?
                       ⟨     ⟩  one of these functions:
                        ⊑         First element of the all-but-the-first list
                         ⋄     or... (see below)
        " are"∾˜               Append that string to the result
" and "∾                       Prepend that string to the result


" others"∾˜•fmt∘≠
                ≠  Length of the all-but-the-first list
           •fmt∘   Format as string
" others"∾˜        Append that string to the result


⊑∾" typing..."∾˜·...
                  ...  Calculate all of the above
                 ·     and then
   " typing..."∾˜      Append that string to the result
⊑∾                     Prepend the first element of the argument list

Python 3, 129 bytes

lambda u:(f"{(u[0])} is"if len(u)<2else f"{u[0]} and {u[1]} are"if len(u)<3else f"{u[0]} and {len(u)-1} others are")+" typing..."

Try it online!

A different approach that was much harder to write is 155 bytes :(

Python 3, 155 bytes

lambda u:f"{u[0]} %s%s%s typing..."%(["and ",""][len(u)<2],""if len(u)<2else u[1]if len(u)<3else len(u)-1,[" are","is"][len(u)<2]+[""," others"][len(u)>2])

Try it online!

Rust, 185 183 162 136 bytes

|v:&[&str]|[v[0],&match v.len(){1=>"is".into(),2=>format!("and {} are",v[1]),l=>format!("and {} others are",l-1)},"typing..."].join(" ")

Try it online!

Ungolfed:

|v: &[&str]| {
        [
            v[0],
            &match v.len() {
                1 => "is".into(),
                2 => format!("and {} are", v[1]),
                l => format!("and {} others are", l - 1),
            },
            "typing...",
        ]
        .join(" ")
    }

Log:

Python3, 95 bytes:

lambda d:f'{d[0]} {"and "+[d[1],f"{l-1} others"][l>2]+" are"if(l:=len(d))>1else"is"} typing...'

This solution requires no pre-formatting of the input list (i.e no unpacking).

Prolog, 108 135 bytes (of which 27 are spent on producing a string)

This was fun, although Prolog does not have very much scope for golfery. My first, unsubmitted, attempt tried to use Prolog's Directed Clause Grammar syntax, but I couldn't get that to be shorter.

IIRC the rules here on CoGoSO correctly, it is permitted to return a string, and not required to print it; so I'm returning in a variable (O), as is traditional in Prolog.

f([X|R],O):-g(R,P),flatten([X,P,typing],Q),atomics_to_string(Q,' ',O).
g([],is).
g(L,[and,C,are]):-L=[Y],C=Y;C=[N,others],length(L,N).

Legend to the one-letter variable names:

% X: first name
% R: Rest of the names
% L: Rest of the names, seen from g/2's perspective
% O: Output
% C: Company, i.e. a word list like '[and, Y]'.
% P: intermediate output: nested list of atoms (representing inner words)
% Q: intermediate result: flat list of atoms

Usage examples:

?- f([jan,piet,klaas,tjoris,korneel], O).
O = "jan and 4 others are typing"

?- f([jan,piet,klaas], O).
O = "jan and 2 others are typing"

?- f([jan,piet], O).
O = "jan and piet are typing"

?- f([jan], O).
O = "jan is typing"

Ungolfed:

f([X|MoreNames], Out) :-
    % InnerWords like 'is' or '[and, Y, are]'
    innerwords(MoreNames, InnerWords),
    % Flatten `[X, [and, [N others], are], typing]`
    % to `[X, and, N, others, are, typing]`
    flatten([X, InnerWords, typing], AllWords),
    % Turn it into a string. This is not a very Prolog thing to
    % do, but the challeng requests it, so here goes.
    atomics_to_string(AllWords, ' ', Out).

% No more others: produce 'is' for 'X is typing'.
innerwords([], is).

% One or more others
innerwords(MoreNames, [and, Company, are]) :-
    % MoreNames is a list of 1, s
    Company=Y, MoreNames=[Y] ;
    % If we reach here, there are 2+ others.
    % (Or our caller is wrongly asking for additional solutions,
    %  and getting wrong answers as a reward.)
    % [and, [N, others], are]
    Company=[N, others], length(MoreNames, N).

SimpleTemplate 0.84, 126 bytes

This full program expects a variable number of arguments passed to the render() method. Argument unpacking can be used for convenience. Each argument is 1 user.

{@echoargv.0}{@ifargc is1} is{@else} and {@ifargc is2}{@echoargv.1}{@else}{@set-T argc,1}{@echoT} others{@/} are{@/} typing...

This will output the text into the standard output, by default.


Ungolfed:

To make it easier to understand, here's an ungolfed version of the code above:

{@echo argv.0}

{@if argc is 1}
    {@echo " is"}
{@else}
    {@echo " and "}
    
    {@if argc is 2}
        {@echo argv.1}
    {@else}
        {@set- count argc, 1}
        {@echo count, " others"}
    {@/}
    
    {@echo " are"}
{@/}

{@echo " typing..."}

About the language:

There are a few important things to keep in mind when reading the code.
This section is important, but code is importanter.


Running the code:

You can run the code on: http://sandbox.onlinephpfunctions.com/code/7233b0a3b8b2fd96924e054944e4ddc850c8f381

Please pick a version between 5.6 and 7.4.13. Can't fix the 8.0.0 compatibility issue without invalidating this answer.

Batch, 170 bytes

@if "%2"=="" echo %~1 is typing...&exit/b
@set s=%~1
@set t=%~2
@set n=2
:l
@if not "%3"=="" set t=%n% others&set/an+=1&shift&goto l
@echo %s% and %t% are typing...

Takes input as command-line parameters. Explanation:

@if "%2"=="" echo %~1 is typing...&exit/b

Handle the case of there only being one user.

@set s=%~1
@set t=%~2
@set n=2

Assume there are two users.

:l
@if not "%3"=="" set t=%n% others&set/an+=1&shift&goto l

While more users are found, update the number of others and increment the total number of users.

@echo %s% and %t% are typing...

Output the users or count as appropriate.

Zsh, 67 bytes

x=(is "and $2 are")
echo $1 ${x[#]-and $[~-#] others are} typing...

Attempt This Online!

Looks up the correct format string into the array $x (one of is, and $2 are, or an unset variable), defaulting to and $[~-#] others are (where $[~-#] is number of arguments - 1), and prints that with the first input $1 and the string typing....


Zsh, 79 bytes

T=\ typing...
1()<<<$1\ is$T
2()<<<"$1 and $2 are$T"
$# $@||2 $1 $[~-#]\ others

Attempt This Online!

Whython, 84 77 bytes

f=lambda a,*b:a+f" {'and %s are'%b?'is'%b} typing..."?f(a,f"{len(b)} others")

Attempt This Online!

This is my first answer in Whython, a horrible modification of Python I made. Its only new feature so far is ?, which is a short-circuiting exception [mis-]handling operator.

Here I use it to handle the case where there are more than 2 people typing: I recurse on the function f with a newly formatted second argument.

This answer also makes use of the (not Whython-specific) fact that you can %-format a string with a tuple of arguments, as long as it's the right number of arguments - and that number can also be 0 or 1 - so both " is"%() and " and %s are"%("second argument",) work fine.

-7 bytes thanks to @ovs: by trying to format and %s are (which requires b to be of length 1), then trying to format is (which requires b to be empty), we can save some bytes by avoiding len().

Python 3, 161 136 bytes

x=input().split(",")
y=len(x)
if y==1:z="is",
if y==2:z="and",x[1],"are"
if y>2:z="and",len(x)-1,"others are"
print(x[0],*z,"typing...")

I FGITW faster.

Try it online!

C (clang) -m32, 109 107 bytes

-2 thanks to ErikF!

t(*n,l){printf(--l?l-1?"%s and %d others are%s":"%s and %s are%s":"%s is%3$s",*n,l-1?l:n[1]," typing...");}

Try it online! Takes input as an array of char* and its length.

C (clang), 115 108 bytes

-7 thanks to ErikF!

t(**n,l){printf(--l?l-1?"%s and %d others are%s":"%s and %s are%s":"%s is%3$s",*n,l-1?l:n[1]," typing...");}

Try it online! Takes input as an array of char* and its length.

C (clang) with -m32, 151 148 144 bytes

To try a different approach from the other C submission, only the list of strings is needed as input for this answer. To get the length of the list, the function is called recursively and output is printed as the function processes the list.

De-golfed:

   g(*s,i) { // type-punning 'char **' as 'int *'
      printf(
        ~i? // is this the first element?
          *s? // (first element) no: end of list?
            i? // (end of list) no: is this the second element?
              "": // (second element) no, don't print if there is only 1 element
              s[1]? // (second element) yes, are there only 2 elements?
                "": // no, don't print second element if there are more than 2
                " and %s": // yes, print second element
            i<2? // (end of list) yes: are there more than 2 elements?
              "": // no
              " and %d others": // yes, print summary
          *s, // (first element) yes: print first element
        *s?
          : // use string if not at end
          i); // otherwise use the list count

      *s?
        g(s+1,i+1): // continue processing list
        printf(" %s typing...", i<2? "is": "are");
   }

   f(s) { g(s,-1); } // initialize list count
g(*s,i){printf(~i?*s?i?"":s[1]?"":" and %s":i<2?"":" and %d others":*s,*s?:i);*s?g(s+1,i+1):printf(" %s typing...",i?"are":"is");}f(s){g(s,-1);}

Try it online!

Ruby, 79 ... 73 bytes

->a,*s{[a,s[0]?[:and,s[1]?[s.size,:others]:s,:are]:"is","typing..."]*" "}

Try it online!

Thanks ovs for -3 bytes

PHP, 94 91 bytes

fn($a)=>"$a[0] ".($a[1]?'and '.($a[2]?count($a)-1 .' others':$a[1]).' are':is).' typing...'

Try it online!

Raw first try at it, probably still golfable. It's a pity PHP cannot handle concatenation or nested ternary conditions without brackets..

EDIT: 3 bytes saved with thanks to Dom Hastings and 640KB's suggestions!

Java, 93 bytes

a->a[0]+(a.length>1?" and "+(a.length>2?a.length-1+" others":a[1])+" are":" is")+" typing..."

Try it online!

APL+WIN, 86 bytes

Prompts for the user list as a nested vector

(,(8⍴2)⊤175+18 4 0[3⌊⍴u])/u[1],('is' 'and'),(⊂↑1↓u),(⊂¯1+⍴u←⎕),'others' 'are' 'typing'

Try it online! Thanks to Dyalog Classic

jq, 99 99+1 (-r flag penalty) = 100 bytes

(length-1)as$l|.[0]as$n|["is","and \(.[1]) are"]|"\($n) \(.[$l]//"and \($l) others are") typing..."

Try it online!

I'm pretty sure there's room to golf this more, but not tonight. :)

  1. Stash the length of the input list in $l. stash the first element in `$n

    (length-1)as$l|.[0]as$n

  2. Generate a new list consisting of two elements, is and is 2nd-input-list-entry are

    |["is","and \(.[1]) are"]

  3. Print the first input list name, then index the list from step 2 by the number of entries in the input minus 1. The // code is called if the index doesn't exist. In that case the alternate text and length-of-input-minus-1 are is used. The typing... string is appended regardless of what other choices were made.

    |"\($n) \(.[$l]//"and \($l) others are") typing..."

Lua, 110 108 99 bytes

a,b,c=...print((b and a.." and "..(c and(#{...}-1 .." others")or b).." are"or a.." is").." typing")

Try it online!

Takes input from command-line arguments, prints to stdout.

Python 2, 102 bytes

i=input()
l=len(i)
print(i[0]+" and "+[`l-1`+" others",i[-1]][l<3]+" are",i[0]+" is")[l<2],"typing..."

Try it online!

Simple nested list slices but takes advantage of no brackets required for printing in Python 2 and also the use of back ticks for the string conversion.

Excel, 100 bytes

=A1&IF(COUNTA(A:A)=1," is"," and "&(IF(COUNTA(A:A)=2,A2,COUNTA(A:A)-1&" others"))&" are")&" typing…"

Dart, 101 Bytes

f(a)=>a[0]+(a.length>1?" and ${a.length>2?"${a.length-1} others":"${a[1]}"} are" : " is")+" typing.";

Try it online

Pari/GP, 89 bytes

a->Str(a[1]if(#a<2," is",Str(" and "if(#a<3,a[2],Str(#a-1" others"))" are"))" typing...")

Try it online!

Charcoal, 48 bytes

S WS⊞υι¿υ«and ¿⊖Lυ⁺Lυ others⊟υ are»is¦ typing...

Try it online! Link is to verbose version of code. Takes input as a list of newline-terminated strings, which for some reason is golfier than taking input as a JSON list. Explanation:

Output the first name and a space.

WS⊞υι

Read in any other names.

¿υ«

If there were any, then:

and 

Output and (with a trailing space).

¿⊖Lυ

If there was more than one other, then...

⁺Lυ others

... concatenate the count to the string others (with a leading space) and output that, otherwise...

⊟υ

... output the last (and only other) name.

 are

Output are (with a leading space).

»is

Otherwise, output is.

 typing...

Output typing... (with a leading space).

Retina 0.8.2, 63 bytes

^.*$
$& is
(¶.*){2,}
¶$#1 others
¶(.*)
 and $1 are
$
 typing...

Try it online! Takes newline-delimited input but test suite splits on commas for convenience. Explanation:

^.*$
$& is

Just one person is typing.

(¶.*){2,}
¶$#1 others

If there are at least two others then replace the list with a single element giving their count.

¶(.*)
 and $1 are

More than one person is typing.

$
 typing...

Complete the sentence.

Haskell, 94 bytes

g(a:x)=a++f x++" typing..."
f[]=" is"
f[x]=" and "++x++" are"
f l=f[show(length l)++" others"]

Try it online!

05AB1E, 42 bytes

ćs©gĀi'€ƒ®ćsgĀi\Ig<'ˆ†]…is€™#Ig≠è“ÜÔ...“ðý

Try it online or verify all test cases.

Explanation:

ć                       # Extract head of the (implicit) input-list; pop and push
                        # remainder-list and first item separately
 s                      # Swap so the remainder-list is at the top
  ©                     # Store it in variable `®` (without popping)
   gĀi                  # Pop and if there are any strings left (2+ inputs):
      '€ƒ              '#  Push dictionary string "and"
         ®              #  Push list `®`
          ćs            #  Extract head and swap again
            gĀi         #  Pop and if there are any strings left (3+ inputs):
               \        #   Discard the head we've pushed
                Ig<     #   Push the input-length - 1
                   'ˆ† '#   Push dictionary string "others"
     ]                  # Close both if-statements
      …is€™             # Push dictionary string "is are"
           #            # Split it on spaces: ["is","are"]
            Ig          # Push the input-length
              ≠         # Check that it's NOT 1 (0 if 1; 1 otherwise)
               è        # Use that to index into the pair
      “ÜÔ...“           # Push dictionary string "typing..."
ðý                      # Join the entire stack with space delimiter
                        # (after which the result is output implicitly)

See this 05AB1E tip of mine (section How to use the dictionary?) to understand why '€ƒ is "and"; 'ˆ† is "others"; …is€™ is "is are"; and “ÜÔ...“ is "typing...".

R, 113 102 bytes

Or R>=4.1, 95 bytes by replacing the word function with \.

function(s,l=length(s)-1,`?`=paste)s[1]?"if"(l,"and"?"if"(l-1,l?"others",s[2])?"are","is")?"typing..."

Try it online!

It appears that paste chains nicely, so we can rename that to ? and use like string addition (with convenient spaces).

Vim, 92 89 bytes

:3,$s/.*/\=line('.')-1.' others'
:1s/\v(\n.+)+/\1
:2s/^/and 
ois typing...<esc>:3s/is/are
V{J

Try it online!

Explanation

:3,$s/.*/\=line('.')-1.' others'<cr>

On every line from line 3 to the end of the buffer (if they exist), replace the contents of the line with the line number minus 1 followed by the string others.

:1s/\v(\n.+)+/\1<cr>

Beginning at (the end of) line 1, find 1 or more matches of a newline followed by some characters. Replace with the last (newline + characters) match.

:2s/^/and <cr>

Add and at the beginning of line 2 (if it exists).

ois typing...<esc>

Open a new line after the cursor (which is always on the last line at the point) and insert is typing....

:3s/is/are

If that new line was line 3, change is to are (if it was line 2, leave unchanged).

V{J

Enter visual line mode, select to the beginning of the buffer, and join lines (space-separated).

Pip, 52 bytes

[POgg?["and"#c?[#g"others"]b"are"]"is""typing..."]Js

(Or 50 bytes in Pip -s, which would use a flag to accomplish the same thing as the Js at the end.)

Takes the names as separate command-line arguments. Try it online!

Explanation

One big list, with the exact contents depending on the number of inputs, joined on spaces:

[                   ; List containing:
  PO g              ;  Pop first element of g (the command-line args)
  g ?               ;  Is g still nonempty?
    [               ;   If so (multiple names case), list containing:
      "and"         ;    This string
      # c ?         ;    Is the third cmdline arg nonempty?
        [           ;     If so (more than 2 names case), list containing:
          # g       ;      Number of elements remaining in g
          "others"  ;      and this string
        ]
        b           ;     Otherwise (2 names case), second cmdline arg
      "are"         ;    and this string
    ]
    "is"            ;   Otherwise (single name case), this string
  "typing..."       ;  and this string
]
J s                 ; Join that list (including all sublists) on space

Python 3, 89 bytes

lambda a,*b:a+f" {b and'and %s are'%[b[0],'%d others'%len(b)][len(b)>1]or'is'} typing..."

Try it online!

-2 bytes thanks to dingledooper

Jelly, 47 bytes

ḢɓL,“¥ṃkŒ»ƊḷḊ?“ and ”;;“@k»ʋ;“ is”$ḷ?ṭ⁹“Æʋ@1'ɼ»

Try it online!

Full program, as it uses Jelly's smash-printing and nilad dumping.

How it works

ḢɓL,“¥ṃkŒ»ƊḷḊ?“ and ”;;“@k»ʋ;“ is”$ḷ?ṭ⁹“Æʋ@1'ɼ» - Main link. Takes a list of usernames U on the left
Ḣ                                               - Chop off the first username, B, and set U' to be U without its head
 ɓ                                              - Start a new dyadic link f(U', B)
                                   ?            - If:
                                  ḷ             -   U' is non-empty
                           ʋ                    - Then:
             ?                                  -   If:
            Ḋ                                   -     U' has more than one element
          Ɗ                                     -   Then:
  L                                             -     Take the length of U'
    “¥ṃkŒ»                                      -     Compressed string: " others"
   ,                                            -     Pair; [len(U'), " others"]
          ḷ                                     -   Else: Yield B
              “ and ”;                          -   Prepend " and "
                      ;“@k»                     -   Append " are"
                                  $             - Else:
                            ;“ is”              -   Append " is"
                                     ṭ⁹         - Tack all of this to the end of B
                                       “Æʋ@1'ɼ» - Unparseable nilad. Smash print the previous stuff, then print " typing..."

Vyxal , 42 41 bytes

ḢḢ[₌hL‹` ÷„`+"]` λ¬ `j`is λ½`⌈?L‹ḃi`Ȯ»...

Try it Online!

Lovely dictionary compression. 44 bytes flagless

Explained

ḢḢ[₌hL‹` ÷„`+"]` λ¬ `j`is λ½`⌈?L‹ḃi`Ȯ»...
ḢḢ                                         # If the length of the input is > 2:
  [₌hL‹                                    #     Push the head of the input and the length of the input - 1
       ` ÷„`+                              #     and append the string " others" to that
             "                             #     and wrap both in a list
              ]                            # endif
               ` λ¬ `j                     # Join the top of the stack on " and "
                      `is λ½`⌈             # Push the list ["is", "are"]
                              ?L‹          # And push the lenght of the input - 1
                                 ḃi        # boolify it to make it 0 or 1 (this is basically just checking if the list length is 1) and index into the list
                                   `Ȯ»...  # Push "typing..." to the stack
                                           # The -ṡ flag joins the stack on spaces. Without it, you'd need `WṄ for +3 bytes.

JavaScript, 85 82 bytes

a=>(b=a.length,a[0]+(b<2?" is":` and ${b<3?a[1]:b-1+" others"} are`)+" typing...")

Try it online!

JavaScript, 77 bytes

a=>a[0]+` ${(l=a.length-1)?`and ${l>1?l+' others':a[1]} are`:'is'} typing...`

Try it online!